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