blob: 92968c549fd49debd3f12732cf30004a9e0d2a3f [file] [log] [blame]
//! 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(())
}