xref: /openbmc/bmcweb/redfish-core/include/utils/sensor_utils.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "dbus_utility.hpp"
6 #include "generated/enums/resource.hpp"
7 #include "generated/enums/sensor.hpp"
8 #include "generated/enums/thermal.hpp"
9 #include "str_utility.hpp"
10 #include "utils/dbus_utils.hpp"
11 #include "utils/json_utils.hpp"
12 
13 #include <boost/url/format.hpp>
14 #include <sdbusplus/unpack_properties.hpp>
15 
16 #include <algorithm>
17 #include <format>
18 #include <functional>
19 #include <optional>
20 #include <ranges>
21 #include <string>
22 #include <string_view>
23 #include <tuple>
24 #include <utility>
25 #include <vector>
26 
27 namespace redfish
28 {
29 namespace sensor_utils
30 {
31 
32 enum class ChassisSubNode
33 {
34     powerNode,
35     sensorsNode,
36     thermalNode,
37     thermalMetricsNode,
38     unknownNode,
39 };
40 
chassisSubNodeToString(ChassisSubNode subNode)41 constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode)
42 {
43     switch (subNode)
44     {
45         case ChassisSubNode::powerNode:
46             return "Power";
47         case ChassisSubNode::sensorsNode:
48             return "Sensors";
49         case ChassisSubNode::thermalNode:
50             return "Thermal";
51         case ChassisSubNode::thermalMetricsNode:
52             return "ThermalMetrics";
53         case ChassisSubNode::unknownNode:
54         default:
55             return "";
56     }
57 }
58 
chassisSubNodeFromString(const std::string & subNodeStr)59 inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr)
60 {
61     // If none match unknownNode is returned
62     ChassisSubNode subNode = ChassisSubNode::unknownNode;
63 
64     if (subNodeStr == "Power")
65     {
66         subNode = ChassisSubNode::powerNode;
67     }
68     else if (subNodeStr == "Sensors")
69     {
70         subNode = ChassisSubNode::sensorsNode;
71     }
72     else if (subNodeStr == "Thermal")
73     {
74         subNode = ChassisSubNode::thermalNode;
75     }
76     else if (subNodeStr == "ThermalMetrics")
77     {
78         subNode = ChassisSubNode::thermalMetricsNode;
79     }
80 
81     return subNode;
82 }
83 
isExcerptNode(const ChassisSubNode subNode)84 inline bool isExcerptNode(const ChassisSubNode subNode)
85 {
86     return (subNode == ChassisSubNode::thermalMetricsNode);
87 }
88 
89 /**
90  * Possible states for physical inventory leds
91  */
92 enum class LedState
93 {
94     OFF,
95     ON,
96     BLINK,
97     UNKNOWN
98 };
99 
100 /**
101  * D-Bus inventory item associated with one or more sensors.
102  */
103 class InventoryItem
104 {
105   public:
InventoryItem(const std::string & objPath)106     explicit InventoryItem(const std::string& objPath) : objectPath(objPath)
107     {
108         // Set inventory item name to last node of object path
109         sdbusplus::message::object_path path(objectPath);
110         name = path.filename();
111         if (name.empty())
112         {
113             BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath);
114         }
115     }
116 
117     std::string objectPath;
118     std::string name;
119     bool isPresent = true;
120     bool isFunctional = true;
121     bool isPowerSupply = false;
122     int powerSupplyEfficiencyPercent = -1;
123     std::string manufacturer;
124     std::string model;
125     std::string partNumber;
126     std::string serialNumber;
127     std::set<std::string> sensors;
128     std::string ledObjectPath;
129     LedState ledState = LedState::UNKNOWN;
130 };
131 
getSensorId(std::string_view sensorName,std::string_view sensorType)132 inline std::string getSensorId(std::string_view sensorName,
133                                std::string_view sensorType)
134 {
135     std::string normalizedType(sensorType);
136     auto remove = std::ranges::remove(normalizedType, '_');
137     normalizedType.erase(std::ranges::begin(remove), normalizedType.end());
138 
139     return std::format("{}_{}", normalizedType, sensorName);
140 }
141 
142 inline std::pair<std::string, std::string>
splitSensorNameAndType(std::string_view sensorId)143     splitSensorNameAndType(std::string_view sensorId)
144 {
145     size_t index = sensorId.find('_');
146     if (index == std::string::npos)
147     {
148         return std::make_pair<std::string, std::string>("", "");
149     }
150     std::string sensorType{sensorId.substr(0, index)};
151     std::string sensorName{sensorId.substr(index + 1)};
152     // fan_pwm and fan_tach need special handling
153     if (sensorType == "fantach" || sensorType == "fanpwm")
154     {
155         sensorType.insert(3, 1, '_');
156     }
157     return std::make_pair(sensorType, sensorName);
158 }
159 
160 namespace sensors
161 {
toReadingUnits(std::string_view sensorType)162 inline std::string_view toReadingUnits(std::string_view sensorType)
163 {
164     if (sensorType == "voltage")
165     {
166         return "V";
167     }
168     if (sensorType == "power")
169     {
170         return "W";
171     }
172     if (sensorType == "current")
173     {
174         return "A";
175     }
176     if (sensorType == "fan_tach")
177     {
178         return "RPM";
179     }
180     if (sensorType == "temperature")
181     {
182         return "Cel";
183     }
184     if (sensorType == "fan_pwm" || sensorType == "utilization" ||
185         sensorType == "humidity")
186     {
187         return "%";
188     }
189     if (sensorType == "altitude")
190     {
191         return "m";
192     }
193     if (sensorType == "airflow")
194     {
195         return "cft_i/min";
196     }
197     if (sensorType == "energy")
198     {
199         return "J";
200     }
201     return "";
202 }
203 
toReadingType(std::string_view sensorType)204 inline sensor::ReadingType toReadingType(std::string_view sensorType)
205 {
206     if (sensorType == "voltage")
207     {
208         return sensor::ReadingType::Voltage;
209     }
210     if (sensorType == "power")
211     {
212         return sensor::ReadingType::Power;
213     }
214     if (sensorType == "current")
215     {
216         return sensor::ReadingType::Current;
217     }
218     if (sensorType == "fan_tach")
219     {
220         return sensor::ReadingType::Rotational;
221     }
222     if (sensorType == "temperature")
223     {
224         return sensor::ReadingType::Temperature;
225     }
226     if (sensorType == "fan_pwm" || sensorType == "utilization")
227     {
228         return sensor::ReadingType::Percent;
229     }
230     if (sensorType == "humidity")
231     {
232         return sensor::ReadingType::Humidity;
233     }
234     if (sensorType == "altitude")
235     {
236         return sensor::ReadingType::Altitude;
237     }
238     if (sensorType == "airflow")
239     {
240         return sensor::ReadingType::AirFlow;
241     }
242     if (sensorType == "energy")
243     {
244         return sensor::ReadingType::EnergyJoules;
245     }
246     return sensor::ReadingType::Invalid;
247 }
248 
249 } // namespace sensors
250 
251 /**
252  * @brief Returns the Redfish State value for the specified inventory item.
253  * @param inventoryItem D-Bus inventory item associated with a sensor.
254  * @param sensorAvailable Boolean representing if D-Bus sensor is marked as
255  * available.
256  * @return State value for inventory item.
257  */
getState(const InventoryItem * inventoryItem,const bool sensorAvailable)258 inline resource::State getState(const InventoryItem* inventoryItem,
259                                 const bool sensorAvailable)
260 {
261     if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
262     {
263         return resource::State::Absent;
264     }
265 
266     if (!sensorAvailable)
267     {
268         return resource::State::UnavailableOffline;
269     }
270 
271     return resource::State::Enabled;
272 }
273 
274 /**
275  * @brief Returns the Redfish Health value for the specified sensor.
276  * @param sensorJson Sensor JSON object.
277  * @param valuesDict Map of all sensor DBus values.
278  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
279  * be nullptr if no associated inventory item was found.
280  * @return Health value for sensor.
281  */
getHealth(nlohmann::json & sensorJson,const dbus::utility::DBusPropertiesMap & valuesDict,const InventoryItem * inventoryItem)282 inline std::string getHealth(nlohmann::json& sensorJson,
283                              const dbus::utility::DBusPropertiesMap& valuesDict,
284                              const InventoryItem* inventoryItem)
285 {
286     // Get current health value (if any) in the sensor JSON object.  Some JSON
287     // objects contain multiple sensors (such as PowerSupplies).  We want to set
288     // the overall health to be the most severe of any of the sensors.
289     std::string currentHealth;
290     auto statusIt = sensorJson.find("Status");
291     if (statusIt != sensorJson.end())
292     {
293         auto healthIt = statusIt->find("Health");
294         if (healthIt != statusIt->end())
295         {
296             std::string* health = healthIt->get_ptr<std::string*>();
297             if (health != nullptr)
298             {
299                 currentHealth = *health;
300             }
301         }
302     }
303 
304     // If current health in JSON object is already Critical, return that.  This
305     // should override the sensor health, which might be less severe.
306     if (currentHealth == "Critical")
307     {
308         return "Critical";
309     }
310 
311     const bool* criticalAlarmHigh = nullptr;
312     const bool* criticalAlarmLow = nullptr;
313     const bool* warningAlarmHigh = nullptr;
314     const bool* warningAlarmLow = nullptr;
315 
316     const bool success = sdbusplus::unpackPropertiesNoThrow(
317         dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh",
318         criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow,
319         "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow",
320         warningAlarmLow);
321 
322     if (success)
323     {
324         // Check if sensor has critical threshold alarm
325         if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) ||
326             (criticalAlarmLow != nullptr && *criticalAlarmLow))
327         {
328             return "Critical";
329         }
330     }
331 
332     // Check if associated inventory item is not functional
333     if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
334     {
335         return "Critical";
336     }
337 
338     // If current health in JSON object is already Warning, return that. This
339     // should override the sensor status, which might be less severe.
340     if (currentHealth == "Warning")
341     {
342         return "Warning";
343     }
344 
345     if (success)
346     {
347         // Check if sensor has warning threshold alarm
348         if ((warningAlarmHigh != nullptr && *warningAlarmHigh) ||
349             (warningAlarmLow != nullptr && *warningAlarmLow))
350         {
351             return "Warning";
352         }
353     }
354 
355     return "OK";
356 }
357 
setLedState(nlohmann::json & sensorJson,const InventoryItem * inventoryItem)358 inline void setLedState(nlohmann::json& sensorJson,
359                         const InventoryItem* inventoryItem)
360 {
361     if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty())
362     {
363         switch (inventoryItem->ledState)
364         {
365             case LedState::OFF:
366                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off;
367                 break;
368             case LedState::ON:
369                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit;
370                 break;
371             case LedState::BLINK:
372                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking;
373                 break;
374             default:
375                 break;
376         }
377     }
378 }
379 
380 /**
381  * @brief Builds a json sensor representation of a sensor.
382  * @param sensorName  The name of the sensor to be built
383  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
384  * build
385  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
386  * @param propertiesDict A dictionary of the properties to build the sensor
387  * from.
388  * @param sensorJson  The json object to fill
389  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
390  * be nullptr if no associated inventory item was found.
391  */
objectPropertiesToJson(std::string_view sensorName,std::string_view sensorType,ChassisSubNode chassisSubNode,const dbus::utility::DBusPropertiesMap & propertiesDict,nlohmann::json & sensorJson,InventoryItem * inventoryItem)392 inline void objectPropertiesToJson(
393     std::string_view sensorName, std::string_view sensorType,
394     ChassisSubNode chassisSubNode,
395     const dbus::utility::DBusPropertiesMap& propertiesDict,
396     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
397 {
398     // Parameter to set to override the type we get from dbus, and force it to
399     // int, regardless of what is available.  This is used for schemas like fan,
400     // that require integers, not floats.
401     bool forceToInt = false;
402 
403     nlohmann::json::json_pointer unit("/Reading");
404 
405     // This ChassisSubNode builds sensor excerpts
406     bool isExcerpt = isExcerptNode(chassisSubNode);
407 
408     /* Sensor excerpts use different keys to reference the sensor. These are
409      * built by the caller.
410      * Additionally they don't include these additional properties.
411      */
412     if (!isExcerpt)
413     {
414         if (chassisSubNode == ChassisSubNode::sensorsNode)
415         {
416             std::string subNodeEscaped = getSensorId(sensorName, sensorType);
417             // For sensors in SensorCollection we set Id instead of MemberId,
418             // including power sensors.
419             sensorJson["Id"] = std::move(subNodeEscaped);
420 
421             std::string sensorNameEs(sensorName);
422             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
423             sensorJson["Name"] = std::move(sensorNameEs);
424         }
425         else if (sensorType != "power")
426         {
427             // Set MemberId and Name for non-power sensors.  For PowerSupplies
428             // and PowerControl, those properties have more general values
429             // because multiple sensors can be stored in the same JSON object.
430             std::string sensorNameEs(sensorName);
431             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
432             sensorJson["Name"] = std::move(sensorNameEs);
433         }
434 
435         const bool* checkAvailable = nullptr;
436         bool available = true;
437         const bool success = sdbusplus::unpackPropertiesNoThrow(
438             dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available",
439             checkAvailable);
440         if (!success)
441         {
442             messages::internalError();
443         }
444         if (checkAvailable != nullptr)
445         {
446             available = *checkAvailable;
447         }
448 
449         sensorJson["Status"]["State"] = getState(inventoryItem, available);
450         sensorJson["Status"]["Health"] =
451             getHealth(sensorJson, propertiesDict, inventoryItem);
452 
453         if (chassisSubNode == ChassisSubNode::sensorsNode)
454         {
455             sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor";
456 
457             sensor::ReadingType readingType =
458                 sensors::toReadingType(sensorType);
459             if (readingType == sensor::ReadingType::Invalid)
460             {
461                 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}",
462                                  sensorType);
463             }
464             else
465             {
466                 sensorJson["ReadingType"] = readingType;
467             }
468 
469             std::string_view readingUnits = sensors::toReadingUnits(sensorType);
470             if (readingUnits.empty())
471             {
472                 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}",
473                                  sensorType);
474             }
475             else
476             {
477                 sensorJson["ReadingUnits"] = readingUnits;
478             }
479         }
480         else if (sensorType == "temperature")
481         {
482             unit = "/ReadingCelsius"_json_pointer;
483             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature";
484             // TODO(ed) Documentation says that path should be type fan_tach,
485             // implementation seems to implement fan
486         }
487         else if (sensorType == "fan" || sensorType == "fan_tach")
488         {
489             unit = "/Reading"_json_pointer;
490             sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM;
491             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
492             setLedState(sensorJson, inventoryItem);
493             forceToInt = true;
494         }
495         else if (sensorType == "fan_pwm")
496         {
497             unit = "/Reading"_json_pointer;
498             sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent;
499             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
500             setLedState(sensorJson, inventoryItem);
501             forceToInt = true;
502         }
503         else if (sensorType == "voltage")
504         {
505             unit = "/ReadingVolts"_json_pointer;
506             sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
507         }
508         else if (sensorType == "power")
509         {
510             std::string lower;
511             std::ranges::transform(sensorName, std::back_inserter(lower),
512                                    bmcweb::asciiToLower);
513             if (lower == "total_power")
514             {
515                 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
516                 // Put multiple "sensors" into a single PowerControl, so have
517                 // generic names for MemberId and Name. Follows Redfish mockup.
518                 sensorJson["MemberId"] = "0";
519                 sensorJson["Name"] = "Chassis Power Control";
520                 unit = "/PowerConsumedWatts"_json_pointer;
521             }
522             else if (lower.find("input") != std::string::npos)
523             {
524                 unit = "/PowerInputWatts"_json_pointer;
525             }
526             else
527             {
528                 unit = "/PowerOutputWatts"_json_pointer;
529             }
530         }
531         else
532         {
533             BMCWEB_LOG_ERROR("Redfish cannot map object type for {}",
534                              sensorName);
535             return;
536         }
537     }
538 
539     // Map of dbus interface name, dbus property name and redfish property_name
540     std::vector<
541         std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
542         properties;
543 
544     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
545 
546     if (!isExcerpt)
547     {
548         if (chassisSubNode == ChassisSubNode::sensorsNode)
549         {
550             properties.emplace_back(
551                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
552                 "/Thresholds/UpperCaution/Reading"_json_pointer);
553             properties.emplace_back(
554                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
555                 "/Thresholds/LowerCaution/Reading"_json_pointer);
556             properties.emplace_back(
557                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
558                 "/Thresholds/UpperCritical/Reading"_json_pointer);
559             properties.emplace_back(
560                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
561                 "/Thresholds/LowerCritical/Reading"_json_pointer);
562 
563             /* Add additional properties specific to sensorType */
564             if (sensorType == "fan_tach")
565             {
566                 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
567                                         "Value", "/SpeedRPM"_json_pointer);
568             }
569         }
570         else if (sensorType != "power")
571         {
572             properties.emplace_back(
573                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
574                 "/UpperThresholdNonCritical"_json_pointer);
575             properties.emplace_back(
576                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
577                 "/LowerThresholdNonCritical"_json_pointer);
578             properties.emplace_back(
579                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
580                 "/UpperThresholdCritical"_json_pointer);
581             properties.emplace_back(
582                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
583                 "/LowerThresholdCritical"_json_pointer);
584         }
585 
586         // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
587 
588         if (chassisSubNode == ChassisSubNode::sensorsNode)
589         {
590             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
591                                     "MinValue",
592                                     "/ReadingRangeMin"_json_pointer);
593             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
594                                     "MaxValue",
595                                     "/ReadingRangeMax"_json_pointer);
596             properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
597                                     "Accuracy", "/Accuracy"_json_pointer);
598         }
599         else if (sensorType == "temperature")
600         {
601             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
602                                     "MinValue",
603                                     "/MinReadingRangeTemp"_json_pointer);
604             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
605                                     "MaxValue",
606                                     "/MaxReadingRangeTemp"_json_pointer);
607         }
608         else if (sensorType != "power")
609         {
610             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
611                                     "MinValue",
612                                     "/MinReadingRange"_json_pointer);
613             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
614                                     "MaxValue",
615                                     "/MaxReadingRange"_json_pointer);
616         }
617     }
618 
619     for (const std::tuple<const char*, const char*,
620                           nlohmann::json::json_pointer>& p : properties)
621     {
622         for (const auto& [valueName, valueVariant] : propertiesDict)
623         {
624             if (valueName != std::get<1>(p))
625             {
626                 continue;
627             }
628 
629             // The property we want to set may be nested json, so use
630             // a json_pointer for easy indexing into the json structure.
631             const nlohmann::json::json_pointer& key = std::get<2>(p);
632 
633             const double* doubleValue = std::get_if<double>(&valueVariant);
634             if (doubleValue == nullptr)
635             {
636                 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
637                 continue;
638             }
639             if (!std::isfinite(*doubleValue))
640             {
641                 if (valueName == "Value")
642                 {
643                     // Readings are allowed to be NAN for unavailable;  coerce
644                     // them to null in the json response.
645                     sensorJson[key] = nullptr;
646                     continue;
647                 }
648                 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
649                                    valueName, *doubleValue);
650                 continue;
651             }
652             if (forceToInt)
653             {
654                 sensorJson[key] = static_cast<int64_t>(*doubleValue);
655             }
656             else
657             {
658                 sensorJson[key] = *doubleValue;
659             }
660         }
661     }
662 }
663 
664 /**
665  * @brief Builds a json sensor excerpt representation of a sensor.
666  *
667  * @details This is a wrapper function to provide consistent setting of
668  * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor
669  * excerpts usually have just the D-Bus path for the sensor that is accepted
670  * and used to build "DataSourceUri".
671 
672  * @param path The D-Bus path to the sensor to be built
673  * @param chassisId The Chassis Id for the sensor
674  * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor
675  * @param sensorTypeExpected The expected type of the sensor
676  * @param propertiesDict A dictionary of the properties to build the sensor
677  * from.
678  * @param sensorJson  The json object to fill
679  * @returns True if sensorJson object filled. False on any error.
680  * Caller is responsible for handling error.
681  */
objectExcerptToJson(const std::string & path,const std::string_view chassisId,ChassisSubNode chassisSubNode,const std::optional<std::string> & sensorTypeExpected,const dbus::utility::DBusPropertiesMap & propertiesDict,nlohmann::json & sensorJson)682 inline bool objectExcerptToJson(
683     const std::string& path, const std::string_view chassisId,
684     ChassisSubNode chassisSubNode,
685     const std::optional<std::string>& sensorTypeExpected,
686     const dbus::utility::DBusPropertiesMap& propertiesDict,
687     nlohmann::json& sensorJson)
688 {
689     if (!isExcerptNode(chassisSubNode))
690     {
691         BMCWEB_LOG_DEBUG("{} is not a sensor excerpt",
692                          chassisSubNodeToString(chassisSubNode));
693         return false;
694     }
695 
696     sdbusplus::message::object_path sensorPath(path);
697     std::string sensorName = sensorPath.filename();
698     std::string sensorType = sensorPath.parent_path().filename();
699     if (sensorName.empty() || sensorType.empty())
700     {
701         BMCWEB_LOG_DEBUG("Invalid sensor path {}", path);
702         return false;
703     }
704 
705     if (sensorTypeExpected && (sensorType != *sensorTypeExpected))
706     {
707         BMCWEB_LOG_DEBUG("{} is not expected type {}", path,
708                          *sensorTypeExpected);
709         return false;
710     }
711 
712     // Sensor excerpts use DataSourceUri to reference full sensor Redfish path
713     sensorJson["DataSourceUri"] =
714         boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId,
715                             getSensorId(sensorName, sensorType));
716 
717     // Fill in sensor excerpt properties
718     objectPropertiesToJson(sensorName, sensorType, chassisSubNode,
719                            propertiesDict, sensorJson, nullptr);
720 
721     return true;
722 }
723 
724 // Maps D-Bus: Service, SensorPath
725 using SensorServicePathMap = std::pair<std::string, std::string>;
726 using SensorServicePathList = std::vector<SensorServicePathMap>;
727 
getAllSensorObjects(const std::string & associatedPath,const std::string & path,std::span<const std::string_view> interfaces,const int32_t depth,std::function<void (const boost::system::error_code & ec,SensorServicePathList &)> && callback)728 inline void getAllSensorObjects(
729     const std::string& associatedPath, const std::string& path,
730     std::span<const std::string_view> interfaces, const int32_t depth,
731     std::function<void(const boost::system::error_code& ec,
732                        SensorServicePathList&)>&& callback)
733 {
734     sdbusplus::message::object_path endpointPath{associatedPath};
735     endpointPath /= "all_sensors";
736 
737     dbus::utility::getAssociatedSubTree(
738         endpointPath, sdbusplus::message::object_path(path), depth, interfaces,
739         [callback = std::move(callback)](
740             const boost::system::error_code& ec,
741             const dbus::utility::MapperGetSubTreeResponse& subtree) {
742             SensorServicePathList sensorsServiceAndPath;
743 
744             if (ec)
745             {
746                 callback(ec, sensorsServiceAndPath);
747                 return;
748             }
749 
750             for (const auto& [sensorPath, serviceMaps] : subtree)
751             {
752                 for (const auto& [service, mapInterfaces] : serviceMaps)
753                 {
754                     sensorsServiceAndPath.emplace_back(service, sensorPath);
755                 }
756             }
757 
758             callback(ec, sensorsServiceAndPath);
759         });
760 }
761 
762 } // namespace sensor_utils
763 } // namespace redfish
764