Skip to main content

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::bmc_quirks::BmcQuirks;
30use crate::patch_support::Payload;
31use crate::patch_support::ReadPatchFn;
32use crate::patches::remove_invalid_resource_state;
33use crate::schema::redfish::environment_metrics::EnvironmentMetrics;
34use crate::schema::redfish::sensor::Sensor as SchemaSensor;
35use crate::Error;
36use crate::NvBmc;
37use nv_redfish_core::Bmc;
38use nv_redfish_core::NavProperty;
39use nv_redfish_core::ODataId;
40use std::sync::Arc;
41
42/// Extracts sensor URIs from metric fields and creates sensor navigation properties.
43///
44/// Handles both single `Option<SensorExcerpt*>` and `Option<Vec<SensorExcerpt*>>` fields.
45/// All `single:` fields must come before `vec:` fields.
46///
47/// # Example
48/// ```ignore
49/// extract_sensor_uris!(metrics,
50///     single: temperature,
51///     single: voltage,
52///     vec: fan_speeds
53/// )
54/// ```
55#[macro_export(local_inner_macros)]
56macro_rules! extract_sensor_uris {
57    ($metrics:expr, $(single: $single_field:ident),* $(, vec: $vec_field:ident)* $(,)?) => {{
58        let mut uris = Vec::new();
59
60        $(
61            if let Some(Some(uri)) = $metrics.$single_field.as_ref()
62                .and_then(|f| f.data_source_uri.as_ref()) {
63                uris.push(uri.clone());
64            }
65        )*
66
67        $(
68            if let Some(items) = &$metrics.$vec_field {
69                for item in items {
70                    if let Some(Some(uri)) = item.data_source_uri.as_ref() {
71                        uris.push(uri.clone());
72                    }
73                }
74            }
75        )*
76
77        $crate::sensor::collect_sensors(uris)
78    }};
79}
80
81struct Config {
82    read_patch_fn: Option<ReadPatchFn>,
83}
84
85impl Config {
86    fn new(quirks: &BmcQuirks) -> Self {
87        let mut patches = Vec::new();
88        if quirks.wrong_resource_status_state() {
89            patches.push(remove_invalid_resource_state);
90        }
91        let read_patch_fn = (!patches.is_empty())
92            .then(|| Arc::new(move |v| patches.iter().fold(v, |acc, f| f(acc))) as ReadPatchFn);
93        Self { read_patch_fn }
94    }
95}
96
97/// Handle for accessing sensor.
98///
99/// This struct provides methods to fetch sensor data from the BMC.
100/// call to [`fetch`](Self::fetch).
101pub struct SensorRef<B: Bmc> {
102    bmc: NvBmc<B>,
103    nav: NavProperty<SchemaSensor>,
104    config: Config,
105}
106
107impl<B: Bmc> SensorRef<B> {
108    /// Create a new sensor handle.
109    ///
110    /// # Arguments
111    ///
112    /// * `nav` - Navigation properties pointing to sensor
113    /// * `bmc` - BMC client for fetching sensor data
114    #[must_use]
115    pub(crate) fn new(bmc: NvBmc<B>, nav: NavProperty<SchemaSensor>) -> Self {
116        let config = Config::new(&bmc.quirks);
117        Self { bmc, nav, config }
118    }
119
120    /// Refresh sensor data from the BMC.
121    ///
122    /// Fetches current sensor readings from the BMC.
123    /// This method performs network I/O and may take time to complete.
124    ///
125    /// # Errors
126    ///
127    /// Returns an error if sensor fetch fails.
128    pub async fn fetch(&self) -> Result<Arc<SchemaSensor>, Error<B>> {
129        if let Some(read_patch_fn) = &self.config.read_patch_fn {
130            Payload::get(self.bmc.as_ref(), &self.nav, read_patch_fn.as_ref()).await
131        } else {
132            self.nav.get(self.bmc.as_ref()).await.map_err(Error::Bmc)
133        }
134    }
135
136    /// `OData` identifier of the `NavProperty<Sensor>` in Redfish.
137    ///
138    /// Typically `/redfish/v1/{Chassis}/Sensors/{ID}`.
139    #[must_use]
140    pub fn odata_id(&self) -> &ODataId {
141        self.nav.id()
142    }
143}
144
145/// Collect sensor refs from URIs
146pub(crate) fn collect_sensors(
147    uris: impl IntoIterator<Item = String>,
148) -> Vec<NavProperty<SchemaSensor>> {
149    uris.into_iter()
150        .map(|uri| NavProperty::<SchemaSensor>::new_reference(ODataId::from(uri)))
151        .collect()
152}
153
154/// Helper function to extract enviroment metrics
155pub(crate) async fn extract_environment_sensors<B: Bmc>(
156    metrics_ref: &NavProperty<EnvironmentMetrics>,
157    bmc: &B,
158) -> Result<Vec<NavProperty<SchemaSensor>>, Error<B>> {
159    metrics_ref
160        .get(bmc)
161        .await
162        .map(|m| {
163            extract_sensor_uris!(m,
164                single: temperature_celsius,
165                single: humidity_percent,
166                single: power_watts,
167                single: energyk_wh,
168                single: power_load_percent,
169                single: dew_point_celsius,
170                single: absolute_humidity,
171                single: energy_joules,
172                single: ambient_temperature_celsius,
173                single: voltage,
174                single: current_amps,
175                vec: fan_speeds_percent
176            )
177        })
178        .map_err(Error::Bmc)
179}