nv_redfish/sensor.rs
1// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16//! Sensor abstraction for Redfish entities.
17//!
18//! This module provides a unified interface for accessing sensor data from
19//! Redfish entities that support modern sensor links. The `HasSensors` trait
20//! is implemented by entities that have associated sensors, and provides access
21//! to a `Sensor` handle for sensor data retrieval.
22//!
23//! # Modern vs Legacy Approach
24//!
25//! This module supports the modern Redfish approach where entities have direct
26//! links to their sensors. For legacy BMCs that only expose sensor data through
27//! `Chassis/Power` and `Chassis/Thermal`, use those explicit endpoints instead.
28
29use crate::schema::redfish::environment_metrics::EnvironmentMetrics;
30use crate::schema::redfish::sensor::Sensor as SchemaSensor;
31use crate::Error;
32use crate::NvBmc;
33use nv_redfish_core::Bmc;
34use nv_redfish_core::NavProperty;
35use nv_redfish_core::ODataId;
36use std::sync::Arc;
37
38/// Extracts sensor URIs from metric fields and creates sensor navigation properties.
39///
40/// Handles both single `Option<SensorExcerpt*>` and `Option<Vec<SensorExcerpt*>>` fields.
41/// All `single:` fields must come before `vec:` fields.
42///
43/// # Example
44/// ```ignore
45/// extract_sensor_uris!(metrics,
46/// single: temperature,
47/// single: voltage,
48/// vec: fan_speeds
49/// )
50/// ```
51#[macro_export(local_inner_macros)]
52macro_rules! extract_sensor_uris {
53 ($metrics:expr, $(single: $single_field:ident),* $(, vec: $vec_field:ident)* $(,)?) => {{
54 let mut uris = Vec::new();
55
56 $(
57 if let Some(Some(uri)) = $metrics.$single_field.as_ref()
58 .and_then(|f| f.data_source_uri.as_ref()) {
59 uris.push(uri.clone());
60 }
61 )*
62
63 $(
64 if let Some(items) = &$metrics.$vec_field {
65 for item in items {
66 if let Some(Some(uri)) = item.data_source_uri.as_ref() {
67 uris.push(uri.clone());
68 }
69 }
70 }
71 )*
72
73 $crate::sensor::collect_sensors(uris)
74 }};
75}
76
77/// Handle for accessing sensor.
78///
79/// This struct provides methods to fetch sensor data from the BMC.
80/// call to [`fetch`](Self::fetch).
81pub struct SensorRef<B: Bmc> {
82 bmc: NvBmc<B>,
83 sensor_ref: NavProperty<SchemaSensor>,
84}
85
86impl<B: Bmc> SensorRef<B> {
87 /// Create a new sensor handle.
88 ///
89 /// # Arguments
90 ///
91 /// * `sensor_ref` - Navigation properties pointing to sensor
92 /// * `bmc` - BMC client for fetching sensor data
93 #[must_use]
94 pub(crate) const fn new(bmc: NvBmc<B>, sensor_ref: NavProperty<SchemaSensor>) -> Self {
95 Self { bmc, sensor_ref }
96 }
97
98 /// Refresh sensor data from the BMC.
99 ///
100 /// Fetches current sensor readings from the BMC.
101 /// This method performs network I/O and may take time to complete.
102 ///
103 /// # Errors
104 ///
105 /// Returns an error if sensor fetch fails.
106 pub async fn fetch(&self) -> Result<Arc<SchemaSensor>, Error<B>> {
107 let sensor = self
108 .sensor_ref
109 .get(self.bmc.as_ref())
110 .await
111 .map_err(Error::Bmc)?;
112
113 Ok(sensor)
114 }
115
116 /// `OData` identifier of the `NavProperty<Sensor>` in Redfish.
117 ///
118 /// Typically `/redfish/v1/{Chassis}/Sensors/{ID}`.
119 #[must_use]
120 pub fn odata_id(&self) -> &ODataId {
121 self.sensor_ref.id()
122 }
123}
124
125/// Collect sensor refs from URIs
126pub(crate) fn collect_sensors(
127 uris: impl IntoIterator<Item = String>,
128) -> Vec<NavProperty<SchemaSensor>> {
129 uris.into_iter()
130 .map(|uri| NavProperty::<SchemaSensor>::new_reference(ODataId::from(uri)))
131 .collect()
132}
133
134/// Helper function to extract enviroment metrics
135pub(crate) async fn extract_environment_sensors<B: Bmc>(
136 metrics_ref: &NavProperty<EnvironmentMetrics>,
137 bmc: &B,
138) -> Result<Vec<NavProperty<SchemaSensor>>, Error<B>> {
139 metrics_ref
140 .get(bmc)
141 .await
142 .map(|m| {
143 extract_sensor_uris!(m,
144 single: temperature_celsius,
145 single: humidity_percent,
146 single: power_watts,
147 single: energyk_wh,
148 single: power_load_percent,
149 single: dew_point_celsius,
150 single: absolute_humidity,
151 single: energy_joules,
152 single: ambient_temperature_celsius,
153 single: voltage,
154 single: current_amps,
155 vec: fan_speeds_percent
156 )
157 })
158 .map_err(Error::Bmc)
159}