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