| //! This module provides functionality for interacting with sensors via D-Bus in an OpenBMC environment. |
| //! It includes functions for retrieving sensor information, mapping Redfish sensors to D-Bus paths, |
| //! and polling sensor values. |
| |
| use crate::dbus_client::ObjectMapper::ObjectMapperProxy; |
| use std::collections::HashMap; |
| use zbus::names::BusName; |
| use zbus::zvariant::ObjectPath; |
| use zbus::Connection; |
| |
| use redfish_codegen::models::odata_v4; |
| use redfish_codegen::models::{ |
| resource::Health, |
| resource::State, |
| resource::Status, |
| sensor::v1_7_0::Sensor as SensorModel, |
| sensor::v1_7_0::{ReadingType, Threshold}, |
| }; |
| |
| /// Retrieves objects with a specific interface from D-Bus. |
| /// |
| /// Equivalent to below sample busctl command |
| /// ~# busctl call xyz.openbmc_project.ObjectMapper /xyz/openbmc_project/object_mapper xyz.openbmc_project.ObjectMapper GetSubTree sias "/xyz/openbmc_project" 0 1 "xyz.openbmc_project.Inventory.Item.Board" |
| /// a{sa{sas}} 8 "/xyz/openbmc_project/inventory/system/board/MyBMC" 1 "xyz.openbmc_project.EntityManager" 7 "xyz.openbmc_project.AddObject" "xyz.openbmc_project.Association.Definitions" "xyz.openbmc_project.Inventory.Connector.Slot" |
| /// |
| /// # Arguments |
| /// |
| /// * `services` - A vector of service names to filter by. If empty, all services are considered. |
| /// * `subtee_root` - The root of the substree to search for. |
| /// * `interface` - The interface name to search for. |
| /// |
| /// # Returns |
| /// |
| /// A `HashMap` where keys are service names and values are vectors of object paths. |
| pub async fn get_objects_with_interface( |
| services: Vec<String>, |
| subtee_root: &str, |
| interface: &str, |
| ) -> Result<HashMap<String, Vec<String>>, Box<dyn std::error::Error>> { |
| let connection = Connection::system().await?; |
| let proxy = ObjectMapperProxy::builder(&connection) |
| .destination("xyz.openbmc_project.ObjectMapper")? |
| .path("/xyz/openbmc_project/object_mapper")? |
| .build() |
| .await?; |
| |
| let depth = 0; |
| let sensor_interface = [interface]; |
| let subtree = proxy |
| .get_sub_tree(subtee_root, depth, &sensor_interface) |
| .await?; |
| |
| let mut objects_by_service: HashMap<String, Vec<String>> = HashMap::new(); |
| |
| for (path, service_map) in subtree.iter() { |
| for service in service_map.keys() { |
| if services.is_empty() || services.contains(service) { |
| objects_by_service |
| .entry(service.clone()) |
| .or_default() |
| .push(path.clone()); |
| } |
| } |
| } |
| |
| Ok(objects_by_service) |
| } |
| |
| /// Retrieves all sensors associated with a specific chassis. |
| /// |
| /// Equivalent to below sample busctl command |
| /// ~# busctl call xyz.openbmc_project.ObjectMapper /xyz/openbmc_project/inventory/system/board/MyBoard/all_sensors org.freedesktop.DBus.Properties Get ss xyz.openbmc_project.Association endpoints |
| /// v as 61 "/xyz/openbmc_project/sensors/temperature/Tray_dT2" "/xyz/openbmc_project/sensors/temperature/Tray_dT1" "/xyz/openbmc_project/sensors/temperature/Tray_dT0" "/xyz/openbmc_project/sensors/temperature/fleeting0" "/xyz/openbmc_project/sensors/temperature/fleeting1" |
| /// |
| /// # Arguments |
| /// |
| /// * `chassis` - The D-Bus path of the chassis. |
| /// |
| /// # Returns |
| /// |
| /// A vector of sensor names. |
| pub async fn get_sensors_under_a_chassis( |
| chassis: &str, |
| ) -> Result<Vec<String>, Box<dyn std::error::Error>> { |
| let connection = Connection::system().await?; |
| let proxy = zbus::fdo::PropertiesProxy::builder(&connection) |
| .destination("xyz.openbmc_project.ObjectMapper")? |
| .path(chassis)? |
| .build() |
| .await?; |
| |
| let interface = zbus_names::InterfaceName::try_from("xyz.openbmc_project.Association")?; |
| let result = proxy.get(interface, "endpoints").await?; |
| |
| let sensors: Vec<String> = result.try_into()?; |
| let sensors = sensors |
| .iter() |
| .map(|path| { |
| let components: Vec<&str> = path.split('/').collect(); |
| if components.len() >= 2 { |
| let sensor_type = components[components.len() - 2]; |
| let sensor_name = components[components.len() - 1]; |
| format!("{}_{}", sensor_type, sensor_name) |
| } else { |
| String::new() |
| } |
| }) |
| .collect(); |
| |
| Ok(sensors) |
| } |
| |
| // ~# busctl call xyz.openbmc_project.ObjectMapper /xyz/openbmc_project/object_mapper xyz.openbmc_project.ObjectMapper GetObject sas "/xyz/openbmc_project/sensors/voltage/P1_SEQ_VDD_CPU0" 0 |
| // a{sas} 2 "xyz.openbmc_project.ObjectMapper" 3 "org.freedesktop.DBus.Introspectable" "org.freedesktop.DBus.Peer" "org.freedesktop.DBus.Properties" "xyz.openbmc_project.PSUSensor" 5 "xyz.openbmc_project.Association.Definitions" "xyz.openbmc_project.Sensor.Threshold.Critical" "xyz.openbmc_project.Sensor.Value" "xyz.openbmc_project.State.Decorator.Availability" "xyz.openbmc_project.State.Decorator.OperationalStatus" |
| async fn fetch_sensor_service_name( |
| connection: &Connection, |
| sensor_path: &str, |
| ) -> Result<(String, Vec<String>), Box<dyn std::error::Error>> { |
| let proxy = ObjectMapperProxy::builder(connection) |
| .destination("xyz.openbmc_project.ObjectMapper")? |
| .path("/xyz/openbmc_project/object_mapper")? |
| .build() |
| .await?; |
| |
| let result = proxy.get_object(sensor_path, &Vec::<&str>::new()).await?; |
| |
| // Search for a service that provides the "xyz.openbmc_project.Sensor.Value" interface |
| let (service_name, interfaces) = result |
| .into_iter() |
| .find(|(_, interfaces)| interfaces.contains(&"xyz.openbmc_project.Sensor.Value".into())) |
| .unwrap_or_default(); // This will default to (String::new(), Vec::new()) if not found |
| |
| Ok((service_name, interfaces)) |
| } |
| |
| /// Maps a Redfish sensor name to its corresponding D-Bus path and service. |
| /// |
| /// Example: map ${sensor_type}_${sensor_name} to D-Bus path as |
| /// "/xyz/openbmc_project/sensors/${sensor_type}/${sensor_name}, |
| /// |
| /// # Arguments |
| /// |
| /// * `connection` - The D-Bus connection. |
| /// * `sensor_name` - The Redfish sensor name. |
| /// |
| /// # Returns |
| /// |
| /// A tuple containing the service name, interfaces, and D-Bus path for the sensor. |
| async fn map_redfish_sensor_to_dbus( |
| connection: &Connection, |
| sensor_name: &str, |
| ) -> Result<((String, Vec<String>), String), Box<dyn std::error::Error>> { |
| let parts: Vec<&str> = sensor_name.splitn(2, '_').collect(); |
| if parts.len() < 2 { |
| return Err(anyhow::anyhow!("Invalid Redfish sensor name pattern").into()); |
| } |
| |
| // WARNING! make "fanpwm_fan1_pwm" to "/xyz/openbmc_project/sensors/fan_pwm/fan1_pwm" |
| let (sensor_type, sensor_name) = if parts[0].starts_with("fan") && parts[1].contains('_') { |
| let fan_parts: Vec<&str> = parts[1].splitn(2, '_').collect(); |
| (format!("fan_{}", fan_parts[0]), fan_parts[1]) |
| } else { |
| (parts[0].to_string(), parts[1]) |
| }; |
| |
| let sensor_path = format!( |
| "/xyz/openbmc_project/sensors/{}/{}", |
| sensor_type, sensor_name |
| ); |
| |
| let sensor_service = fetch_sensor_service_name(connection, &sensor_path).await?; |
| |
| Ok((sensor_service, sensor_path)) |
| } |
| |
| /// Converts a D-Bus unit to a Redfish reading type. |
| /// |
| /// # Arguments |
| /// |
| /// * `dbus_unit` - The D-Bus unit string. |
| /// |
| /// # Returns |
| /// |
| /// An `Option<ReadingType>` corresponding to the D-Bus unit. |
| fn dbus_unit_to_redfish_reading_type(dbus_unit: &str) -> Option<ReadingType> { |
| match dbus_unit { |
| "xyz.openbmc_project.Sensor.Value.Unit.DegreesC" => Some(ReadingType::Temperature), |
| "xyz.openbmc_project.Sensor.Value.Unit.PercentRH" => Some(ReadingType::Humidity), |
| "xyz.openbmc_project.Sensor.Value.Unit.Watts" => Some(ReadingType::Power), |
| "xyz.openbmc_project.Sensor.Value.Unit.Joules" => Some(ReadingType::EnergyJoules), |
| "xyz.openbmc_project.Sensor.Value.Unit.Volts" => Some(ReadingType::Voltage), |
| "xyz.openbmc_project.Sensor.Value.Unit.Amperes" => Some(ReadingType::Current), |
| "xyz.openbmc_project.Sensor.Value.Unit.RPMS" => Some(ReadingType::Rotational), |
| "xyz.openbmc_project.Sensor.Value.Unit.Pascals" => Some(ReadingType::Pressure), |
| "xyz.openbmc_project.Sensor.Value.Unit.Meters" => Some(ReadingType::Altitude), |
| "xyz.openbmc_project.Sensor.Value.Unit.Percent" => Some(ReadingType::Percent), |
| "xyz.openbmc_project.Sensor.Value.Unit.CFM" => Some(ReadingType::AirFlow), |
| _ => None, |
| } |
| } |
| |
| // ~# busctl call xyz.openbmc_project.PSUSensor /xyz/openbmc_project/sensors/voltage/P1_SEQ_VDD_CPU0 org.freedesktop.DBus.Properties GetAll s "xyz.openbmc_project.Sensor.Value" |
| // a{sv} 4 "Unit" s "xyz.openbmc_project.Sensor.Value.Unit.Volts" "MaxValue" d 1.02 "MinValue" d 0 "Value" d 0.927 |
| async fn get_values( |
| proxy: &zbus::fdo::PropertiesProxy<'_>, |
| sensor_model: &mut SensorModel, |
| ) -> Result<(), Box<dyn std::error::Error>> { |
| let interface = zbus_names::InterfaceName::try_from("xyz.openbmc_project.Sensor.Value")?; |
| let result: HashMap<String, zbus::zvariant::OwnedValue> = proxy.get_all(interface).await?; |
| |
| if let Some(value) = result.get("Unit") { |
| let value: zbus::zvariant::Value = zbus::zvariant::Value::from(value); |
| let unit: String = String::try_from(value)?; |
| sensor_model.reading_type = dbus_unit_to_redfish_reading_type(&unit); |
| sensor_model.reading_units = Some(unit); |
| } |
| if let Some(value) = result.get("MaxValue") { |
| let value: zbus::zvariant::Value = zbus::zvariant::Value::from(value); |
| let max_value = f64::try_from(value)?; |
| sensor_model.reading_range_max = Some(max_value); |
| } |
| if let Some(value) = result.get("MinValue") { |
| let value: zbus::zvariant::Value = zbus::zvariant::Value::from(value); |
| let min_value = f64::try_from(value)?; |
| sensor_model.reading_range_min = Some(min_value); |
| } |
| if let Some(value) = result.get("Value") { |
| let value: zbus::zvariant::Value = zbus::zvariant::Value::from(value); |
| let value = f64::try_from(value)?; |
| sensor_model.reading = Some(value); |
| } |
| |
| Ok(()) |
| } |
| |
| // ~# busctl call xyz.openbmc_project.PSUSensor /xyz/openbmc_project/sensors/voltage/P1_SEQ_VDD_CPU0 org.freedesktop.DBus.Properties GetAll s "xyz.openbmc_project.Association.Definitions" |
| // a{sv} 1 "Associations" a(sss) 2 "inventory" "sensors" "/xyz/openbmc_project/inventory/system/board/BoardName" "chassis" "all_sensors" "/xyz/openbmc_project/inventory/system/board/BoardName" |
| async fn get_association( |
| proxy: &zbus::fdo::PropertiesProxy<'_>, |
| sensor_model: &mut SensorModel, |
| ) -> Result<(), Box<dyn std::error::Error>> { |
| let interface = |
| zbus_names::InterfaceName::try_from("xyz.openbmc_project.Association.Definitions")?; |
| let result: HashMap<String, zbus::zvariant::OwnedValue> = proxy.get_all(interface).await?; |
| if let Some(associations_value) = result.get("Associations") { |
| let associations_value: zbus::zvariant::Value = |
| zbus::zvariant::Value::from(associations_value); |
| if let zbus::zvariant::Value::Array(v) = associations_value { |
| let associations: Vec<(String, String, String)> = Vec::try_from(v)?; |
| if let Some((_, _, chassis)) = associations.first() { |
| sensor_model.related_item = Some(vec![odata_v4::IdRef { |
| odata_id: Some(odata_v4::Id(chassis.to_owned())), |
| }]); |
| } |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| // ~# busctl call xyz.openbmc_project.PSUSensor /xyz/openbmc_project/sensors/voltage/P1_SEQ_VDD_CPU0 org.freedesktop.DBus.Properties GetAll s "xyz.openbmc_project.Sensor.Threshold.Critical" |
| // a{sv} 4 "CriticalHigh" d 1.02 "CriticalAlarmHigh" b false "CriticalLow" d 0.68 "CriticalAlarmLow" b false |
| async fn get_threshhold( |
| proxy: &zbus::fdo::PropertiesProxy<'_>, |
| sensor_model: &mut SensorModel, |
| ) -> Result<(), Box<dyn std::error::Error>> { |
| let threshold_interface = |
| zbus_names::InterfaceName::try_from("xyz.openbmc_project.Sensor.Threshold.Critical")?; |
| let threshold_result: HashMap<String, zbus::zvariant::OwnedValue> = |
| proxy.get_all(threshold_interface).await?; |
| |
| let mut thresholds = redfish_codegen::models::sensor::v1_7_0::Thresholds::default(); |
| |
| if let Some(value) = threshold_result.get("CriticalHigh") { |
| let value: zbus::zvariant::Value = zbus::zvariant::Value::from(value); |
| let critical_high = f64::try_from(value)?; |
| thresholds.upper_critical = Some(Threshold { |
| reading: Some(critical_high), |
| ..Default::default() |
| }); |
| } |
| if let Some(value) = threshold_result.get("CriticalAlarmHigh") { |
| let value: zbus::zvariant::Value = zbus::zvariant::Value::from(value); |
| let _critical_alarm_high: bool = bool::try_from(value)?; |
| } |
| if let Some(value) = threshold_result.get("CriticalLow") { |
| let value: zbus::zvariant::Value = zbus::zvariant::Value::from(value); |
| let critical_low = f64::try_from(value)?; |
| thresholds.lower_critical = Some(Threshold { |
| reading: Some(critical_low), |
| ..Default::default() |
| }); |
| } |
| if let Some(value) = threshold_result.get("CriticalAlarmLow") { |
| let value: zbus::zvariant::Value = zbus::zvariant::Value::from(value); |
| let _critical_alarm_low: bool = bool::try_from(value)?; |
| } |
| sensor_model.thresholds = Some(thresholds); |
| // TODO! did not find reading for LowerCaution and UpperCaution |
| |
| Ok(()) |
| } |
| |
| // ~# busctl call xyz.openbmc_project.PSUSensor /xyz/openbmc_project/sensors/voltage/P1_SEQ_VDD_CPU0 org.freedesktop.DBus.Properties GetAll s "xyz.openbmc_project.State.Decorator.Availability" |
| // a{sv} 1 "Available" b true |
| async fn get_availablity( |
| proxy: &zbus::fdo::PropertiesProxy<'_>, |
| sensor_model: &mut SensorModel, |
| ) -> Result<(), Box<dyn std::error::Error>> { |
| let interface = |
| zbus_names::InterfaceName::try_from("xyz.openbmc_project.State.Decorator.Availability")?; |
| let result: HashMap<String, zbus::zvariant::OwnedValue> = proxy.get_all(interface).await?; |
| |
| if let Some(value) = result.get("Available") { |
| let value: zbus::zvariant::Value = zbus::zvariant::Value::from(value); |
| let availability: bool = bool::try_from(value)?; |
| if sensor_model.status.is_none() { |
| sensor_model.status = Some(Status::default()); |
| } |
| if let Some(ref mut status) = sensor_model.status { |
| status.state = Some(if availability { |
| State::Enabled |
| } else { |
| State::Disabled |
| }); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| // ~# busctl call xyz.openbmc_project.PSUSensor /xyz/openbmc_project/sensors/voltage/P1_SEQ_VDD_CPU0 org.freedesktop.DBus.Properties GetAll s "xyz.openbmc_project.State.Decorator.OperationalStatus" |
| // a{sv} 1 "Functional" b true |
| async fn get_operational( |
| proxy: &zbus::fdo::PropertiesProxy<'_>, |
| sensor_model: &mut SensorModel, |
| ) -> Result<(), Box<dyn std::error::Error>> { |
| let interface = zbus_names::InterfaceName::try_from( |
| "xyz.openbmc_project.State.Decorator.OperationalStatus", |
| )?; |
| let result: HashMap<String, zbus::zvariant::OwnedValue> = proxy.get_all(interface).await?; |
| |
| if let Some(value) = result.get("Functional") { |
| let value: zbus::zvariant::Value = zbus::zvariant::Value::from(value); |
| let operational: bool = bool::try_from(value)?; |
| if sensor_model.status.is_none() { |
| sensor_model.status = Some(Status::default()); |
| } |
| if let Some(ref mut status) = sensor_model.status { |
| status.health = Some(if operational { |
| Health::OK |
| } else { |
| Health::Critical |
| }); |
| } |
| } |
| |
| Ok(()) |
| } |
| |
| /// Retrieves sensor information for a single sensor and updates the provided `SensorModel`. |
| /// |
| /// # Arguments |
| /// |
| /// * `sensor_name` - The name of the sensor. |
| /// * `sensor_model` - A mutable reference to the `SensorModel` to be updated. |
| /// |
| /// # Returns |
| /// |
| /// `Ok(())` if successful, or an error if the operation fails. |
| pub async fn get_one_sensor( |
| sensor_name: &str, |
| sensor_model: &mut SensorModel, |
| ) -> Result<(), Box<dyn std::error::Error>> { |
| let connection = Connection::system().await?; |
| let (sensor_services, sensor_path) = |
| map_redfish_sensor_to_dbus(&connection, sensor_name).await?; |
| let (service_name, interfaces) = sensor_services; |
| let sensor_service = BusName::try_from(service_name)?; |
| let sensor_path = ObjectPath::try_from(sensor_path)?; |
| let proxy = zbus::fdo::PropertiesProxy::builder(&connection) |
| .destination(&sensor_service)? |
| .path(sensor_path)? |
| .build() |
| .await?; |
| |
| get_values(&proxy, sensor_model).await?; |
| if interfaces.contains(&"xyz.openbmc_project.Association.Definitions".to_string()) { |
| get_association(&proxy, sensor_model).await?; |
| } |
| if interfaces.contains(&"xyz.openbmc_project.Sensor.Threshold.Critical".to_string()) { |
| get_threshhold(&proxy, sensor_model).await?; |
| } |
| if interfaces.contains(&"xyz.openbmc_project.State.Decorator.Availability".to_string()) { |
| get_availablity(&proxy, sensor_model).await?; |
| } |
| if interfaces.contains(&"xyz.openbmc_project.State.Decorator.OperationalStatus".to_string()) { |
| get_operational(&proxy, sensor_model).await?; |
| } |
| |
| Ok(()) |
| } |