| //! This module provides functionality for extracting sensor configurations from OpenBMC Entity |
| //! Manager's configuration files |
| |
| use lazy_static::lazy_static; |
| use log::debug; |
| use serde::{Deserialize, Serialize}; |
| use serde_json::Value; |
| use std::collections::HashMap; |
| use std::fs; |
| use std::io; |
| use std::path::{Path, PathBuf}; |
| |
| /// Represents the type of reading a sensor provides. |
| #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)] |
| pub enum ReadingType { |
| Voltage, |
| Current, |
| Power, |
| Temperature, |
| Fan, |
| #[default] |
| Unknown, |
| } |
| |
| impl std::fmt::Display for ReadingType { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| match self { |
| ReadingType::Voltage => write!(f, "Voltage"), |
| ReadingType::Current => write!(f, "Current"), |
| ReadingType::Power => write!(f, "Power"), |
| ReadingType::Temperature => write!(f, "Temperature"), |
| ReadingType::Fan => write!(f, "Fan"), |
| ReadingType::Unknown => write!(f, "Unknown"), |
| } |
| } |
| } |
| |
| /// Represents properties of a Power Supply Unit (PSU) sensor. |
| #[derive(Debug, Clone, Default, Deserialize, Serialize)] |
| pub struct PSUProperty { |
| pub name: String, |
| pub max: i32, |
| pub min: i32, |
| pub scale: i32, |
| pub offset: i32, |
| } |
| |
| /// Represents a sensor configuration. |
| #[allow(non_snake_case)] |
| #[derive(Debug, Clone, Default, Deserialize, Serialize)] |
| pub struct Sensor { |
| #[serde(default)] |
| pub Name: String, |
| #[serde(rename = "Type")] |
| pub sensor_type: String, |
| #[serde(default)] |
| pub Bus: Option<String>, |
| #[serde(default)] |
| pub Address: Option<String>, |
| #[serde(default)] |
| /// Use Labels[0] to select from this for the threshold for this sensor |
| pub Thresholds: Option<Vec<Threshold>>, |
| #[serde(default)] |
| pub Index: Option<u32>, |
| #[serde(default)] |
| pub Labels: Option<Vec<String>>, |
| #[serde(default)] |
| pub extra: HashMap<String, Value>, |
| #[serde(default)] |
| pub reading_type: (PSUProperty, ReadingType), |
| } |
| |
| /// Represents a threshold configuration for a sensor. |
| #[allow(non_snake_case)] |
| #[derive(Debug, Clone, Default, Deserialize, Serialize)] |
| pub struct Threshold { |
| pub Direction: String, |
| pub Name: String, |
| pub Severity: u32, |
| pub Value: f64, |
| #[serde(default)] |
| pub Index: Option<u32>, |
| #[serde(default)] |
| pub Label: Option<String>, |
| } |
| |
| #[rustfmt::skip] |
| lazy_static! { |
| static ref LABEL_MATCH: HashMap<&'static str, (PSUProperty, ReadingType)> = { |
| let mut m = HashMap::new(); |
| m.insert("pin", (PSUProperty { name: "Input Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("pin1", (PSUProperty { name: "Input Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("pin2", (PSUProperty { name: "Input Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("pout1", (PSUProperty { name: "Output Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("pout2", (PSUProperty { name: "Output Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("pout3", (PSUProperty { name: "Output Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("power1", (PSUProperty { name: "Output Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("power2", (PSUProperty { name: "Output Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("power3", (PSUProperty { name: "Output Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("power4", (PSUProperty { name: "Output Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("maxpin", (PSUProperty { name: "Max Input Power".to_string(), max: 3000, min: 0, scale: 6, offset: 0 }, ReadingType::Power)); |
| m.insert("vin", (PSUProperty { name: "Input Voltage".to_string(), max: 300, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vin1", (PSUProperty { name: "Input Voltage".to_string(), max: 300, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vin2", (PSUProperty { name: "Input Voltage".to_string(), max: 300, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("maxvin", (PSUProperty { name: "Max Input Voltage".to_string(), max: 300, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in_voltage0", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in_voltage1", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in_voltage2", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in_voltage3", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout1", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout2", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout3", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout4", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout5", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout6", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout7", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout8", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout9", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout10", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout11", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout12", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout13", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout14", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout15", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout16", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout17", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout18", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout19", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout20", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout21", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout22", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout23", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout24", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout25", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout26", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout27", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout28", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout29", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout30", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout31", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vout32", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("vmon", (PSUProperty { name: "Auxiliary Input Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in0", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in1", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in2", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in3", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in4", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in5", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in6", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("in7", (PSUProperty { name: "Output Voltage".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Voltage)); |
| m.insert("iin", (PSUProperty { name: "Input Current".to_string(), max: 20, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iin1", (PSUProperty { name: "Input Current".to_string(), max: 20, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iin2", (PSUProperty { name: "Input Current".to_string(), max: 20, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout1", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout2", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout3", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout4", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout5", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout6", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout7", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout8", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout9", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout10", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout11", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout12", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout13", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("iout14", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("curr1", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("curr2", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("curr3", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("curr4", (PSUProperty { name: "Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("maxiout1", (PSUProperty { name: "Max Output Current".to_string(), max: 255, min: 0, scale: 3, offset: 0 }, ReadingType::Current)); |
| m.insert("temp1", (PSUProperty { name: "Temperature".to_string(), max: 127, min: -128, scale: 3, offset: 0 }, ReadingType::Temperature)); |
| m.insert("temp2", (PSUProperty { name: "Temperature".to_string(), max: 127, min: -128, scale: 3, offset: 0 }, ReadingType::Temperature)); |
| m.insert("temp3", (PSUProperty { name: "Temperature".to_string(), max: 127, min: -128, scale: 3, offset: 0 }, ReadingType::Temperature)); |
| m.insert("temp4", (PSUProperty { name: "Temperature".to_string(), max: 127, min: -128, scale: 3, offset: 0 }, ReadingType::Temperature)); |
| m.insert("temp5", (PSUProperty { name: "Temperature".to_string(), max: 127, min: -128, scale: 3, offset: 0 }, ReadingType::Temperature)); |
| m.insert("temp6", (PSUProperty { name: "Temperature".to_string(), max: 127, min: -128, scale: 3, offset: 0 }, ReadingType::Temperature)); |
| m.insert("maxtemp1", (PSUProperty { name: "Max Temperature".to_string(), max: 127, min: -128, scale: 3, offset: 0 }, ReadingType::Temperature)); |
| m.insert("fan1", (PSUProperty { name: "Fan Speed 1".to_string(), max: 30000, min: 0, scale: 0, offset: 0 }, ReadingType::Fan)); |
| m.insert("fan2", (PSUProperty { name: "Fan Speed 2".to_string(), max: 30000, min: 0, scale: 0, offset: 0 }, ReadingType::Fan)); |
| m.insert("fan3", (PSUProperty { name: "Fan Speed 3".to_string(), max: 30000, min: 0, scale: 0, offset: 0 }, ReadingType::Fan)); |
| m.insert("fan4", (PSUProperty { name: "Fan Speed 4".to_string(), max: 30000, min: 0, scale: 0, offset: 0 }, ReadingType::Fan)); |
| m |
| }; |
| } |
| |
| /// Infers the reading type of a sensor based on its configuration. |
| /// |
| /// # Arguments |
| /// |
| /// * `sensor` - The sensor configuration to infer the reading type from. |
| /// |
| /// # Returns |
| /// |
| /// A tuple containing the PSUProperty and ReadingType for the sensor. |
| fn infer_reading_type(sensor: &Sensor) -> (PSUProperty, ReadingType) { |
| // First, try to match using the Labels field |
| if let Some(labels) = &sensor.Labels { |
| if let Some(first_label) = labels.first() { |
| if let Some((psu_property, reading_type)) = LABEL_MATCH.get(first_label.as_str()) { |
| return (psu_property.clone(), *reading_type); |
| } |
| |
| // If no exact match, try to match prefixes |
| for (label, (psu_property, reading_type)) in LABEL_MATCH.iter() { |
| if first_label.starts_with(label) { |
| return (psu_property.clone(), *reading_type); |
| } |
| } |
| } |
| } |
| |
| // If Labels matching fails, fall back to matching based on the sensor Name |
| let name_lower = sensor.Name.to_lowercase(); |
| |
| // Fallback logic |
| if name_lower.contains("temp") || name_lower.ends_with('t') { |
| if let Some((psu_property, reading_type)) = LABEL_MATCH.get("temp1") { |
| return (psu_property.clone(), *reading_type); |
| } |
| } else if name_lower.contains("curr") |
| || name_lower.contains("iout") |
| || name_lower.ends_with('i') |
| { |
| if let Some((psu_property, reading_type)) = LABEL_MATCH.get("curr1") { |
| return (psu_property.clone(), *reading_type); |
| } |
| } else if name_lower.contains("power") |
| || name_lower.contains("pout") |
| || name_lower.contains("pin") |
| { |
| if let Some((psu_property, reading_type)) = LABEL_MATCH.get("power1") { |
| return (psu_property.clone(), *reading_type); |
| } |
| } else if name_lower.contains("volt") |
| || name_lower.contains("vin") |
| || name_lower.contains("vout") |
| || name_lower.ends_with('v') |
| { |
| if let Some((psu_property, reading_type)) = LABEL_MATCH.get("vin") { |
| return (psu_property.clone(), *reading_type); |
| } |
| } else if name_lower.contains("fan") || name_lower.contains("tach") { |
| if let Some((psu_property, reading_type)) = LABEL_MATCH.get("fan1") { |
| return (psu_property.clone(), *reading_type); |
| } |
| } |
| |
| (PSUProperty::default(), ReadingType::Unknown) |
| } |
| |
| /// Extracts sensor configurations from the 'Exposes' field of a configuration object. |
| /// |
| /// # Arguments |
| /// |
| /// * `exposes` - A slice of JSON Value objects containing sensor configurations. |
| /// |
| /// # Returns |
| /// |
| /// A vector of Sensor structs extracted from the configuration. |
| #[rustfmt::skip] |
| fn extract_sensors_from_exposes(exposes: &[Value]) -> Vec<Sensor> { |
| let mut sensors = Vec::new(); |
| |
| for expose in exposes { |
| debug!("Captured expose: {:?}", expose); |
| // Handle cases where there are multiple sensors defined within a single expose object, each corresponding to a label, like "P0_ADM1266". |
| if let Some(labels) = expose.get("Labels").and_then(|v| v.as_array()) { |
| for label in labels { |
| debug!("Captured expose by label: {:?}", label); |
| if let Some(label_name) = label.as_str() { |
| let name_key = format!("{}_Name", label_name); |
| let sensor_name = expose |
| .get(&name_key) |
| .and_then(|v| v.as_str()) |
| .unwrap_or(label_name) |
| .to_string(); |
| |
| let mut sensor = Sensor { |
| Name: sensor_name.clone(), |
| sensor_type: expose |
| .get("Type") |
| .and_then(|v| v.as_str()) |
| .unwrap_or("") |
| .to_string(), |
| Bus: expose.get("Bus").and_then(|v| match v { |
| Value::String(s) => Some(s.clone()), |
| Value::Number(n) => n.as_u64().map(|n| n.to_string()), |
| _ => None, |
| }), |
| Address: expose |
| .get("Address") |
| .and_then(|v| v.as_str().map(String::from)), |
| Thresholds: expose |
| .get("Thresholds") |
| .and_then(|v| serde_json::from_value(v.clone()).ok()), |
| Index: None, |
| Labels: Some(vec![label_name.to_string()]), |
| extra: HashMap::new(), |
| reading_type: Default::default(), |
| }; |
| |
| sensor.reading_type = infer_reading_type(&sensor); |
| debug!("Add captured expose by labels: {:?}", sensor); |
| sensors.push(sensor); |
| } |
| } |
| } |
| // Try to deserialize the entire expose object into a Sensor struct, like "fan2_tach". |
| else if let Ok(mut sensor) = serde_json::from_value::<Sensor>(expose.clone()) { |
| debug!("Captured expose by Sensor type: {:?}", expose); |
| // Filter for valid sensor types, some entry in config file are not sensors, like |
| // MAX31790, use this to filter them out |
| let valid_hwmon_types = [ |
| "ADC", "MAX31725", "MAX31730", "TMP75", "TMP421", "TMP441", "TMP112", "EMC1412", |
| "EMC1413", "ADM1032", "ADT7470", "G781", "MAX6581", "MAX6654", "LM75", "LM95245", |
| "ADT7460", "ADT7490", "W83795G", "SI7020", "NCT7802", "pmbus", "ADM1266", "ADM1272", |
| "ADM1275", "ADM1278", "ADM1293", "ADM1294", "IR35221", "IR38062", "IR38164", "IR38263", |
| "ISL68137", "ISL68220", "ISL69225", "ISL69227", "ISL69228", "ISL69234", "ISL69236", |
| "ISL69239", "ISL69243", "ISL69247", "ISL69254", "ISL69259", "ISL69260", "LM25066", |
| "RAA228000", "RAA228004", "RAA228006", "RAA228228", "RAA229001", "RAA229004", |
| "TPS53679", "TPS53688", "TPS53681", "TPS53622", "TPS53819", "TPS53915", "TPS53916", |
| "TPS53647", "TPS53667", "TPS53676", "TPS53661", "TPS53641", "TPS53631", "TPS53625", |
| "TPS53M30", "UCD90120", "UCD90124", "UCD90160", "UCD90320", "UCD9090", "UCD90910", |
| "XDPE11280", "XDPE12284", "XDPE132G5C", "MAX20730", "MAX20734", "MAX20743", |
| "MAX34451", "MAX34460", "MAX34461", "MAX77836", "HwmonTempSensor", "VirtualSensor", |
| "ADCSensor", "FanSensor", "NVMe", "TMP431", "I2CFan" |
| ]; |
| |
| let valid_pldm_types = ["PLDM"]; |
| |
| let valid_other_types = [ |
| "ExternalSensor", "Fan", "System", "DownstreamPort", |
| "MyMachine CPLD Upstream Port", "MyMachine 12 Upstream Port", |
| "MyMachine 13 Upstream Port", "MyMachine 8 Upstream Port", |
| "MyMachine 11 Upstream Port" |
| ]; |
| |
| if valid_hwmon_types.contains(&sensor.sensor_type.as_str()) { |
| // Handle I2CFan type specifically |
| if sensor.sensor_type == "I2CFan" { |
| if let Some(connector) = expose.get("Connector") { |
| if let Some(pwm_name) = connector.get("PwmName").and_then(|v| v.as_str()) { |
| let mut pwm_sensor = sensor.clone(); |
| pwm_sensor.Name = pwm_name.to_string(); |
| pwm_sensor.sensor_type = "PWMSensor".to_string(); |
| pwm_sensor.reading_type = infer_reading_type(&pwm_sensor); |
| debug!("Add PWM sensor: {:?}", pwm_sensor); |
| sensors.push(pwm_sensor); |
| } |
| } |
| } |
| sensor.reading_type = infer_reading_type(&sensor); |
| debug!("Add captured expose {:?} by Sensor type, sensor: {:?}", expose, sensor); |
| } else if valid_pldm_types.contains(&sensor.sensor_type.as_str()) { |
| sensor.sensor_type = "PLDM".to_string(); |
| sensor.reading_type = infer_reading_type(&sensor); |
| } else if valid_other_types.contains(&sensor.sensor_type.as_str()) { |
| // FIXME: hardcode sensor NAme for PWMSensor from config |
| // Add captured expose by Sensor type: Object {"HotPluggable": Bool(false), "LocationType": String("Connector"), "Name": String("Fan3"), "PWMSensor": String("fan3_pwm"), "ServiceLabel": String("FAN3"), "TachSensor": String("fan3_tach"), "Type": String("Fan")} |
| // extract_sensors, config /usr/share/entity-manager/configurations/heizo.json, all sensors from config: Sensor { Name: "Fan0", sensor_type: "Fan", Bus: None, Address: None, Thresholds: None, Index: None, Labels: None, extra: {}, reading_type: (PSUProperty { name: "Fan Speed 1", max: 30000, min: 0, scale: 0, offset: 0 }, Fan) } |
| if sensor.sensor_type == "Fan" { |
| let pwm_sensor = expose.get("PWMSensor").and_then(|p| p.as_str()).unwrap_or("").to_owned(); |
| sensor.Name = pwm_sensor; |
| sensor.sensor_type = "PWMSensor".to_string(); |
| } |
| } else { |
| debug!("Skip captured expose: {:?}", expose); |
| continue; // Skip sensors with unrecognized types |
| } |
| |
| sensor.reading_type = infer_reading_type(&sensor); |
| debug!("Add captured expose {:?} by Sensor type, sensor: {:?}", expose, sensor); |
| sensors.push(sensor); |
| } |
| // A catch-all for sensors that don't fit into the first two categories. |
| else if let Some(sensor_type) = expose.get("Type").and_then(|t| t.as_str()) { |
| debug!("Captured expose by Type: {:?}", expose); |
| |
| let labels = expose.get("Labels").and_then(|v| v.as_array()).map(|arr| { |
| arr.iter() |
| .filter_map(|v| v.as_str()) |
| .map(String::from) |
| .collect::<Vec<String>>() |
| }); |
| |
| let mut label_map = HashMap::new(); |
| if let Some(labels) = &labels { |
| for label in labels { |
| let name_key = format!("{}_Name", label); |
| if let Some(sensor_name) = expose.get(&name_key).and_then(|v| v.as_str()) { |
| label_map.insert(label.clone(), sensor_name.to_string()); |
| } |
| } |
| } |
| |
| let bus = expose.get("Bus").and_then(|b| match b { |
| Value::Number(n) => Some(n.to_string()), |
| Value::String(s) => Some(s.clone()), |
| _ => None, |
| }); |
| let address = expose |
| .get("Address") |
| .and_then(|a| a.as_str().map(String::from)); |
| |
| // Create individual sensors for each label |
| for (label, sensor_name) in label_map { |
| let mut thresholds = Vec::new(); |
| if let Some(thresh_array) = expose.get("Thresholds").and_then(|t| t.as_array()) { |
| for thresh in thresh_array { |
| if let Ok(t) = serde_json::from_value::<Threshold>(thresh.clone()) { |
| if t.Label.as_ref().map(|l| l == &label).unwrap_or(false) { |
| thresholds.push(t); |
| } |
| } |
| } |
| } |
| |
| let mut sensor = Sensor { |
| reading_type: Default::default(), |
| Name: sensor_name.clone(), |
| sensor_type: sensor_type.to_string(), |
| Bus: bus.clone(), |
| Address: address.clone(), |
| Thresholds: if thresholds.is_empty() { |
| None |
| } else { |
| Some(thresholds) |
| }, |
| Index: None, |
| Labels: Some(vec![label]), |
| extra: HashMap::new(), |
| }; |
| sensor.reading_type = infer_reading_type(&sensor); |
| |
| debug!("Add captured expose by Type and label: {:?}", sensor); |
| sensors.push(sensor); |
| } |
| } |
| } |
| |
| sensors |
| } |
| |
| /// Recursively extracts sensor configurations from a JSON Value. |
| /// |
| /// # Arguments |
| /// |
| /// * `value` - A JSON Value that may contain sensor configurations. |
| /// |
| /// # Returns |
| /// |
| /// A vector of Sensor structs extracted from the Value. |
| fn extract_sensors_from_value(value: &Value) -> Vec<Sensor> { |
| match value { |
| Value::Array(arr) => arr.iter().flat_map(extract_sensors_from_value).collect(), |
| Value::Object(_) => extract_sensors_from_exposes(&[value.clone()]), |
| _ => Vec::new(), |
| } |
| } |
| |
| /// Extracts sensor configurations from a JSON configuration file. |
| /// |
| /// # Arguments |
| /// |
| /// * `config_file` - The path to the JSON configuration file. |
| /// |
| /// # Returns |
| /// |
| /// A Result containing a vector of Sensor structs or an error. |
| pub async fn extract_sensors(config_file: &str) -> Result<Vec<Sensor>, Box<dyn std::error::Error>> { |
| let file_content = tokio::fs::read_to_string(config_file).await?; |
| let config: Value = serde_json::from_str(&file_content)?; |
| |
| let sensors = match config { |
| Value::Array(configs) => { |
| let mut all_sensors = Vec::new(); |
| for c in configs { |
| if let Some(exposes) = c.get("Exposes") { |
| all_sensors.extend(extract_sensors_from_value(exposes)); |
| } |
| } |
| all_sensors |
| } |
| Value::Object(config) => { |
| if let Some(exposes) = config.get("Exposes") { |
| extract_sensors_from_value(exposes) |
| } else { |
| Vec::new() |
| } |
| } |
| _ => Vec::new(), |
| }; |
| |
| Ok(sensors) |
| } |
| |
| /// Finds the sysfs file path for a given sensor. |
| /// |
| /// # Arguments |
| /// |
| /// * `sensor` - The Sensor configuration to find the file for. |
| /// |
| /// # Returns |
| /// |
| /// A Result containing the sysfs file path as a String or an error. |
| pub fn find_sensor_file(sensor: &Sensor) -> io::Result<String> { |
| debug!("find_sensor_file for sensor {:?}", sensor); |
| if let (Some(bus), Some(address)) = (&sensor.Bus, &sensor.Address) { |
| // Strip "0x" prefix if present |
| let address_stripped = address.trim_start_matches("0x"); |
| |
| let base_path = Path::new("/sys/class/hwmon"); |
| let result = search_hwmon(base_path, bus, address_stripped, sensor); |
| if result.is_ok() { |
| debug!( |
| "find_sensor_file in hwmon for sensor {:?}, found result {:?}", |
| sensor, result |
| ); |
| return result; |
| } |
| |
| // For /sys/bus/i2c/devices |
| let base_path = Path::new("/sys/bus/i2c/devices"); |
| let result = search_i2c(base_path, bus, address_stripped, sensor); |
| if result.is_ok() { |
| debug!( |
| "find_sensor_file in i2c for sensor {:?}, found result {:?}", |
| sensor, result |
| ); |
| return result; |
| } |
| } |
| |
| Err(io::Error::new( |
| io::ErrorKind::NotFound, |
| "Sensor file not found", |
| )) |
| } |
| |
| /// Searches for a sensor file in the hwmon directory. |
| /// |
| /// # Arguments |
| /// |
| /// * `base_path` - The base path to start the search from. |
| /// * `bus` - The bus identifier. |
| /// * `address_stripped` - The stripped address of the sensor. |
| /// * `sensor` - The Sensor configuration to search for. |
| /// |
| /// # Returns |
| /// |
| /// A Result containing the sensor file path as a String or an error. |
| fn search_hwmon( |
| base_path: &Path, |
| bus: &str, |
| address_stripped: &str, |
| sensor: &Sensor, |
| ) -> io::Result<String> { |
| let entries = fs::read_dir(base_path)?; |
| |
| for entry in entries { |
| let entry = entry?; |
| let symlink_path = entry.path(); |
| let real_path = fs::read_link(&symlink_path)?; |
| let full_real_path = base_path.join(&real_path); |
| |
| if let Some(path_str) = full_real_path.to_str() { |
| if path_str.contains(bus) && path_str.contains(address_stripped) { |
| let sensor_file = find_specific_sensor_file(&full_real_path, sensor)?; |
| return Ok(sensor_file.to_string_lossy().into_owned()); |
| } |
| } |
| } |
| Err(io::Error::new( |
| io::ErrorKind::NotFound, |
| "Sensor file not found in hwmon", |
| )) |
| } |
| |
| /// Searches for a sensor file in the i2c devices directory. |
| /// |
| /// # Arguments |
| /// |
| /// * `base_path` - The base path to start the search from. |
| /// * `bus` - The bus identifier. |
| /// * `address` - The address of the sensor. |
| /// * `sensor` - The Sensor configuration to search for. |
| /// |
| /// # Returns |
| /// |
| /// A Result containing the sensor file path as a String or an error. |
| fn search_i2c(base_path: &Path, bus: &str, address: &str, sensor: &Sensor) -> io::Result<String> { |
| let i2c_path = base_path.join(format!("i2c-{}", bus)); |
| let device_path = i2c_path.join(format!("{}-00{}", bus, address)); |
| |
| debug!( |
| "search_i2c: hwmon_dir {:?} for sensor {:?}", |
| device_path.join("hwmon"), |
| sensor.Name |
| ); |
| let hwmon_dir = fs::read_dir(device_path.join("hwmon"))? |
| .filter_map(Result::ok) |
| .next() |
| .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Hwmon directory not found"))?; |
| |
| let sensor_file = find_specific_sensor_file(&hwmon_dir.path(), sensor)?; |
| Ok(sensor_file.to_string_lossy().into_owned()) |
| } |
| |
| /// Finds the specific sensor file within a hwmon directory. |
| /// |
| /// # Arguments |
| /// |
| /// * `hwmon_path` - The path to the hwmon directory. |
| /// * `sensor` - The Sensor configuration to search for. |
| /// |
| /// # Returns |
| /// |
| /// A Result containing the PathBuf of the sensor file or an error. |
| fn find_specific_sensor_file(hwmon_path: &Path, sensor: &Sensor) -> io::Result<PathBuf> { |
| let hwmon_dir = fs::read_dir(hwmon_path)?; |
| let file_pattern = get_file_pattern(sensor); |
| debug!( |
| "Matching file_pattern {:?} under hwmon_dir {:?} for sensor name {}", |
| file_pattern, hwmon_dir, sensor.Name |
| ); |
| |
| for file in hwmon_dir { |
| let file = file?; |
| if file.file_name().to_string_lossy().contains(&file_pattern) { |
| debug!( |
| "Matching file {:?} under hwmon_path: {:?} for sensor name {}", |
| file, hwmon_path, sensor.Name |
| ); |
| return Ok(file.path()); |
| } |
| } |
| |
| Err(io::Error::new( |
| io::ErrorKind::NotFound, |
| "Specific sensor file not found", |
| )) |
| } |
| |
| /// Gets the file pattern for a normal (non-pmbus) sensor. |
| /// |
| /// # Arguments |
| /// |
| /// * `sensor` - The Sensor configuration to get the pattern for. |
| /// |
| /// # Returns |
| /// |
| /// A String containing the file pattern for the sensor. |
| fn get_file_pattern_normal(sensor: &Sensor) -> String { |
| let mut index = 1; // Default index if not specified |
| |
| if let Some(sensor_index) = sensor.Index { |
| index = sensor_index; |
| } else if let Some(labels) = &sensor.Labels { |
| // Assert that there's only one label |
| // SAFEFY: this should only be called at server initialization, so it can unwrap |
| assert!( |
| labels.len() == 1, |
| "Expected exactly one label, found {}", |
| labels.len() |
| ); |
| |
| // Extract the number suffix from the label |
| if let Some(label) = labels.first() { |
| if let Ok(number_str) = label |
| .chars() |
| .skip_while(|c| !c.is_numeric()) |
| .collect::<String>() |
| .parse::<u32>() |
| { |
| index = number_str; |
| } |
| } |
| } |
| |
| debug!("sensor {} has type {}", sensor.Name, sensor.sensor_type); |
| match sensor.reading_type.1 { |
| ReadingType::Voltage => format!("in{}_input", index), |
| ReadingType::Current => format!("curr{}_input", index), |
| ReadingType::Power => format!("power{}_input", index), |
| ReadingType::Temperature => format!("temp{}_input", index), |
| ReadingType::Fan => format!("fan{}_input", index + 1), |
| _ => format!("in{}_input", index), // Default to voltage for unknown types |
| } |
| } |
| |
| /// Gets the file pattern for a sensor. |
| /// |
| /// # Arguments |
| /// |
| /// * `sensor` - The Sensor configuration to get the pattern for. |
| /// |
| /// # Returns |
| /// |
| /// A String containing the file pattern for the sensor. |
| fn get_file_pattern(sensor: &Sensor) -> String { |
| match sensor.sensor_type.as_str() { |
| // pmbus has special mapping rule, not worthy to include them in the normal |
| // handling logic |
| "pmbus" => { |
| if let Some(labels) = &sensor.Labels { |
| if let Some(label) = labels.first() { |
| match label.as_str() { |
| "vout1" => "in2_input".to_string(), |
| "iout1" => "curr2_input".to_string(), |
| "vin" => "in1_input".to_string(), |
| "pin" | "pout1" => "power2_input".to_string(), |
| "temp1" => "temp1_input".to_string(), |
| _ => "in1_input".to_string(), |
| } |
| } else { |
| "in1_input".to_string() |
| } |
| } else { |
| "in1_input".to_string() |
| } |
| } |
| _ => |
| // logic for normal sensor types |
| { |
| get_file_pattern_normal(sensor) |
| } |
| } |
| } |
| |
| /// Represents Field Replaceable Unit (FRU) information for a sensor. |
| #[derive(Debug, Clone)] |
| pub struct FruInfo { |
| /// FRU name for this device |
| pub service_label: Option<String>, |
| /// FRU name of its parent device, if this is an embedded device |
| pub location_code: Option<String>, |
| /// The Concatenation of all Parent FRU's ServiceLabels until root |
| #[allow(dead_code)] |
| pub part_location_context: Option<String>, |
| } |
| |
| /// Extracts FRU information for sensors from a configuration file. |
| /// |
| /// TODO: not fully verified yet |
| /// |
| /// # Arguments |
| /// |
| /// * `config_file` - The path to the configuration file. |
| /// |
| /// # Returns |
| /// |
| /// A Result containing a HashMap of sensor names to FruInfo or an error. |
| pub fn extract_sensor_frus( |
| config_file: &str, |
| ) -> Result<HashMap<String, FruInfo>, Box<dyn std::error::Error>> { |
| let file_content = std::fs::read_to_string(config_file)?; |
| let config: Value = serde_json::from_str(&file_content)?; |
| |
| let mut sensor_frus = HashMap::new(); |
| let mut sensor_to_fan_map = HashMap::new(); |
| |
| if let Value::Array(configs) = config { |
| for c in configs { |
| process_component(&c, &mut sensor_frus, &mut sensor_to_fan_map)?; |
| } |
| } |
| |
| // Update sensor FRU info with fan service labels |
| let fan_service_labels: HashMap<_, _> = sensor_frus |
| .iter() |
| .filter_map(|(name, info)| { |
| info.service_label |
| .as_ref() |
| .map(|label| (name.clone(), label.clone())) |
| }) |
| .collect(); |
| |
| for (sensor_name, fan_name) in sensor_to_fan_map { |
| if let Some(fru_info) = sensor_frus.get_mut(&sensor_name) { |
| if let Some(service_label) = fan_service_labels.get(&fan_name) { |
| fru_info.service_label = Some(service_label.clone()); |
| } |
| } |
| } |
| |
| Ok(sensor_frus) |
| } |
| |
| /// Processes a component from the configuration and extracts sensor FRU information. |
| /// |
| /// TODO: not fully verified yet |
| /// |
| /// # Arguments |
| /// |
| /// * `component` - The JSON Value representing the component. |
| /// * `sensor_frus` - A mutable reference to the HashMap storing sensor FRU information. |
| /// * `sensor_to_fan_map` - A mutable reference to the HashMap mapping sensors to fans. |
| /// |
| /// # Returns |
| /// |
| /// A Result indicating success or an error. |
| fn process_component( |
| component: &Value, |
| sensor_frus: &mut HashMap<String, FruInfo>, |
| sensor_to_fan_map: &mut HashMap<String, String>, |
| ) -> Result<(), Box<dyn std::error::Error>> { |
| if let Some(location_code) = component |
| .get("xyz.openbmc_project.Inventory.Decorator.LocationCode") |
| .and_then(|l| l.get("LocationCode")) |
| .and_then(|l| l.as_str()) |
| { |
| let fru_info = FruInfo { |
| location_code: Some(location_code.to_string()), |
| service_label: None, |
| part_location_context: None, |
| }; |
| |
| if let Some(exposes) = component.get("Exposes").and_then(|e| e.as_array()) { |
| for expose in exposes { |
| if let Some(expose_type) = expose.get("Type").and_then(|t| t.as_str()) { |
| match expose_type { |
| "Fan" => { |
| let fan_name = |
| expose.get("Name").and_then(|n| n.as_str()).unwrap_or(""); |
| let service_label = expose.get("ServiceLabel").and_then(|s| s.as_str()); |
| let tach_sensor = expose.get("TachSensor").and_then(|t| t.as_str()); |
| let pwm_sensor = expose.get("PWMSensor").and_then(|p| p.as_str()); |
| |
| let mut fan_fru_info = fru_info.clone(); |
| fan_fru_info.service_label = service_label.map(String::from); |
| |
| sensor_frus.insert(fan_name.to_string(), fan_fru_info); |
| |
| if let Some(tach) = tach_sensor { |
| sensor_to_fan_map.insert(tach.to_string(), fan_name.to_string()); |
| } |
| if let Some(pwm) = pwm_sensor { |
| sensor_to_fan_map.insert(pwm.to_string(), fan_name.to_string()); |
| } |
| } |
| _ => { |
| extract_sensors_from_expose(expose, sensor_frus, &fru_info); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| /// Extracts sensor information from an 'Expose' object in the configuration. |
| /// |
| /// TODO: not fully verified yet |
| /// |
| /// # Arguments |
| /// |
| /// * `expose` - The JSON Value representing the 'Expose' object. |
| /// * `sensor_frus` - A mutable reference to the HashMap storing sensor FRU information. |
| /// * `fru_info` - The FruInfo for the current component. |
| fn extract_sensors_from_expose( |
| expose: &Value, |
| sensor_frus: &mut HashMap<String, FruInfo>, |
| fru_info: &FruInfo, |
| ) { |
| if let Some(sensor_type) = expose.get("Type").and_then(|t| t.as_str()) { |
| match sensor_type { |
| "ADM1266" | "pmbus" | "ADM1272" => { |
| if let Some(labels) = expose.get("Labels").and_then(|l| l.as_array()) { |
| for label in labels { |
| if let Some(label_str) = label.as_str() { |
| let name_key = format!("{}_Name", label_str); |
| if let Some(sensor_name) = |
| expose.get(&name_key).and_then(|n| n.as_str()) |
| { |
| sensor_frus.insert(sensor_name.to_string(), fru_info.clone()); |
| } |
| } |
| } |
| } |
| } |
| "PLDM" | "MAX31725" | "TMP431" | "I2CFan" | "ADC" | "ExternalSensor" => { |
| if let Some(sensor_name) = expose.get("Name").and_then(|n| n.as_str()) { |
| sensor_frus.insert(sensor_name.to_string(), fru_info.clone()); |
| } |
| } |
| _ => {} |
| } |
| } |
| } |