xref: /openbmc/bmcweb/redfish-core/include/utils/sensor_utils.hpp (revision f664fd8abeba11c7aa06d5119423a27b9d40d045)
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     if (sensorType == "liquidflow")
212     {
213         return "L/min";
214     }
215     if (sensorType == "pressure")
216     {
217         return "Pa";
218     }
219     return "";
220 }
221 
toReadingType(std::string_view sensorType)222 inline sensor::ReadingType toReadingType(std::string_view sensorType)
223 {
224     if (sensorType == "voltage")
225     {
226         return sensor::ReadingType::Voltage;
227     }
228     if (sensorType == "power")
229     {
230         return sensor::ReadingType::Power;
231     }
232     if (sensorType == "current")
233     {
234         return sensor::ReadingType::Current;
235     }
236     if (sensorType == "fan_tach")
237     {
238         return sensor::ReadingType::Rotational;
239     }
240     if (sensorType == "temperature")
241     {
242         return sensor::ReadingType::Temperature;
243     }
244     if (sensorType == "fan_pwm" || sensorType == "utilization")
245     {
246         return sensor::ReadingType::Percent;
247     }
248     if (sensorType == "humidity")
249     {
250         return sensor::ReadingType::Humidity;
251     }
252     if (sensorType == "altitude")
253     {
254         return sensor::ReadingType::Altitude;
255     }
256     if (sensorType == "airflow")
257     {
258         return sensor::ReadingType::AirFlow;
259     }
260     if (sensorType == "energy")
261     {
262         return sensor::ReadingType::EnergyJoules;
263     }
264     if (sensorType == "liquidflow")
265     {
266         return sensor::ReadingType::LiquidFlowLPM;
267     }
268     if (sensorType == "pressure")
269     {
270         return sensor::ReadingType::PressurePa;
271     }
272     return sensor::ReadingType::Invalid;
273 }
274 
275 } // namespace sensors
276 
277 /**
278  * @brief Returns the Redfish State value for the specified inventory item.
279  * @param inventoryItem D-Bus inventory item associated with a sensor.
280  * @param sensorAvailable Boolean representing if D-Bus sensor is marked as
281  * available.
282  * @return State value for inventory item.
283  */
getState(const InventoryItem * inventoryItem,const bool sensorAvailable)284 inline resource::State getState(const InventoryItem* inventoryItem,
285                                 const bool sensorAvailable)
286 {
287     if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
288     {
289         return resource::State::Absent;
290     }
291 
292     if (!sensorAvailable)
293     {
294         return resource::State::UnavailableOffline;
295     }
296 
297     return resource::State::Enabled;
298 }
299 
300 /**
301  * @brief Returns the Redfish Health value for the specified sensor.
302  * @param sensorJson Sensor JSON object.
303  * @param valuesDict Map of all sensor DBus values.
304  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
305  * be nullptr if no associated inventory item was found.
306  * @return Health value for sensor.
307  */
getHealth(nlohmann::json & sensorJson,const dbus::utility::DBusPropertiesMap & valuesDict,const InventoryItem * inventoryItem)308 inline std::string getHealth(nlohmann::json& sensorJson,
309                              const dbus::utility::DBusPropertiesMap& valuesDict,
310                              const InventoryItem* inventoryItem)
311 {
312     // Get current health value (if any) in the sensor JSON object.  Some JSON
313     // objects contain multiple sensors (such as PowerSupplies).  We want to set
314     // the overall health to be the most severe of any of the sensors.
315     std::string currentHealth;
316     auto statusIt = sensorJson.find("Status");
317     if (statusIt != sensorJson.end())
318     {
319         auto healthIt = statusIt->find("Health");
320         if (healthIt != statusIt->end())
321         {
322             std::string* health = healthIt->get_ptr<std::string*>();
323             if (health != nullptr)
324             {
325                 currentHealth = *health;
326             }
327         }
328     }
329 
330     // If current health in JSON object is already Critical, return that.  This
331     // should override the sensor health, which might be less severe.
332     if (currentHealth == "Critical")
333     {
334         return "Critical";
335     }
336 
337     const bool* criticalAlarmHigh = nullptr;
338     const bool* criticalAlarmLow = nullptr;
339     const bool* warningAlarmHigh = nullptr;
340     const bool* warningAlarmLow = nullptr;
341 
342     const bool success = sdbusplus::unpackPropertiesNoThrow(
343         dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh",
344         criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow,
345         "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow",
346         warningAlarmLow);
347 
348     if (success)
349     {
350         // Check if sensor has critical threshold alarm
351         if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) ||
352             (criticalAlarmLow != nullptr && *criticalAlarmLow))
353         {
354             return "Critical";
355         }
356     }
357 
358     // Check if associated inventory item is not functional
359     if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
360     {
361         return "Critical";
362     }
363 
364     // If current health in JSON object is already Warning, return that. This
365     // should override the sensor status, which might be less severe.
366     if (currentHealth == "Warning")
367     {
368         return "Warning";
369     }
370 
371     if (success)
372     {
373         // Check if sensor has warning threshold alarm
374         if ((warningAlarmHigh != nullptr && *warningAlarmHigh) ||
375             (warningAlarmLow != nullptr && *warningAlarmLow))
376         {
377             return "Warning";
378         }
379     }
380 
381     return "OK";
382 }
383 
setLedState(nlohmann::json & sensorJson,const InventoryItem * inventoryItem)384 inline void setLedState(nlohmann::json& sensorJson,
385                         const InventoryItem* inventoryItem)
386 {
387     if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty())
388     {
389         switch (inventoryItem->ledState)
390         {
391             case LedState::OFF:
392                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off;
393                 break;
394             case LedState::ON:
395                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit;
396                 break;
397             case LedState::BLINK:
398                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking;
399                 break;
400             default:
401                 break;
402         }
403     }
404 }
405 
406 /**
407  * @brief Builds a json sensor representation of a sensor.
408  * @param sensorName  The name of the sensor to be built
409  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
410  * build
411  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
412  * @param propertiesDict A dictionary of the properties to build the sensor
413  * from.
414  * @param sensorJson  The json object to fill
415  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
416  * be nullptr if no associated inventory item was found.
417  */
objectPropertiesToJson(std::string_view sensorName,std::string_view sensorType,ChassisSubNode chassisSubNode,const dbus::utility::DBusPropertiesMap & propertiesDict,nlohmann::json & sensorJson,InventoryItem * inventoryItem)418 inline void objectPropertiesToJson(
419     std::string_view sensorName, std::string_view sensorType,
420     ChassisSubNode chassisSubNode,
421     const dbus::utility::DBusPropertiesMap& propertiesDict,
422     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
423 {
424     // Parameter to set to override the type we get from dbus, and force it to
425     // int, regardless of what is available.  This is used for schemas like fan,
426     // that require integers, not floats.
427     bool forceToInt = false;
428 
429     nlohmann::json::json_pointer unit("/Reading");
430 
431     // This ChassisSubNode builds sensor excerpts
432     bool isExcerpt = isExcerptNode(chassisSubNode);
433 
434     /* Sensor excerpts use different keys to reference the sensor. These are
435      * built by the caller.
436      * Additionally they don't include these additional properties.
437      */
438     if (!isExcerpt)
439     {
440         if (chassisSubNode == ChassisSubNode::sensorsNode)
441         {
442             std::string subNodeEscaped = getSensorId(sensorName, sensorType);
443             // For sensors in SensorCollection we set Id instead of MemberId,
444             // including power sensors.
445             sensorJson["Id"] = std::move(subNodeEscaped);
446 
447             std::string sensorNameEs(sensorName);
448             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
449             sensorJson["Name"] = std::move(sensorNameEs);
450         }
451         else if (sensorType != "power")
452         {
453             // Set MemberId and Name for non-power sensors.  For PowerSupplies
454             // and PowerControl, those properties have more general values
455             // because multiple sensors can be stored in the same JSON object.
456             std::string sensorNameEs(sensorName);
457             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
458             sensorJson["Name"] = std::move(sensorNameEs);
459         }
460 
461         const bool* checkAvailable = nullptr;
462         bool available = true;
463         const bool success = sdbusplus::unpackPropertiesNoThrow(
464             dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available",
465             checkAvailable);
466         if (!success)
467         {
468             messages::internalError();
469         }
470         if (checkAvailable != nullptr)
471         {
472             available = *checkAvailable;
473         }
474 
475         sensorJson["Status"]["State"] = getState(inventoryItem, available);
476         sensorJson["Status"]["Health"] =
477             getHealth(sensorJson, propertiesDict, inventoryItem);
478 
479         if (chassisSubNode == ChassisSubNode::sensorsNode)
480         {
481             sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor";
482 
483             sensor::ReadingType readingType =
484                 sensors::toReadingType(sensorType);
485             if (readingType == sensor::ReadingType::Invalid)
486             {
487                 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}",
488                                  sensorType);
489             }
490             else
491             {
492                 sensorJson["ReadingType"] = readingType;
493             }
494 
495             std::string_view readingUnits = sensors::toReadingUnits(sensorType);
496             if (readingUnits.empty())
497             {
498                 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}",
499                                  sensorType);
500             }
501             else
502             {
503                 sensorJson["ReadingUnits"] = readingUnits;
504             }
505         }
506         else if (sensorType == "temperature")
507         {
508             unit = "/ReadingCelsius"_json_pointer;
509             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature";
510             // TODO(ed) Documentation says that path should be type fan_tach,
511             // implementation seems to implement fan
512         }
513         else if (sensorType == "fan" || sensorType == "fan_tach")
514         {
515             unit = "/Reading"_json_pointer;
516             sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM;
517             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
518             if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_INDICATORLED)
519             {
520                 setLedState(sensorJson, inventoryItem);
521             }
522             forceToInt = true;
523         }
524         else if (sensorType == "fan_pwm")
525         {
526             unit = "/Reading"_json_pointer;
527             sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent;
528             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
529             if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_INDICATORLED)
530             {
531                 setLedState(sensorJson, inventoryItem);
532             }
533             forceToInt = true;
534         }
535         else if (sensorType == "voltage")
536         {
537             unit = "/ReadingVolts"_json_pointer;
538             sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
539         }
540         else if (sensorType == "power")
541         {
542             std::string lower;
543             std::ranges::transform(sensorName, std::back_inserter(lower),
544                                    bmcweb::asciiToLower);
545             if (lower == "total_power")
546             {
547                 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
548                 // Put multiple "sensors" into a single PowerControl, so have
549                 // generic names for MemberId and Name. Follows Redfish mockup.
550                 sensorJson["MemberId"] = "0";
551                 sensorJson["Name"] = "Chassis Power Control";
552                 unit = "/PowerConsumedWatts"_json_pointer;
553             }
554             else if (lower.find("input") != std::string::npos)
555             {
556                 unit = "/PowerInputWatts"_json_pointer;
557             }
558             else
559             {
560                 unit = "/PowerOutputWatts"_json_pointer;
561             }
562         }
563         else
564         {
565             BMCWEB_LOG_ERROR("Redfish cannot map object type for {}",
566                              sensorName);
567             return;
568         }
569     }
570 
571     // Map of dbus interface name, dbus property name and redfish property_name
572     std::vector<
573         std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
574         properties;
575 
576     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
577 
578     if (!isExcerpt)
579     {
580         if (chassisSubNode == ChassisSubNode::sensorsNode)
581         {
582             properties.emplace_back(
583                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
584                 "/Thresholds/UpperCaution/Reading"_json_pointer);
585             properties.emplace_back(
586                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
587                 "/Thresholds/LowerCaution/Reading"_json_pointer);
588             properties.emplace_back(
589                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
590                 "/Thresholds/UpperCritical/Reading"_json_pointer);
591             properties.emplace_back(
592                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
593                 "/Thresholds/LowerCritical/Reading"_json_pointer);
594             properties.emplace_back(
595                 "xyz.openbmc_project.Sensor.Threshold.HardShutdown",
596                 "HardShutdownHigh",
597                 "/Thresholds/UpperFatal/Reading"_json_pointer);
598             properties.emplace_back(
599                 "xyz.openbmc_project.Sensor.Threshold.HardShutdown",
600                 "HardShutdownLow",
601                 "/Thresholds/LowerFatal/Reading"_json_pointer);
602 
603             /* Add additional properties specific to sensorType */
604             if (sensorType == "fan_tach")
605             {
606                 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
607                                         "Value", "/SpeedRPM"_json_pointer);
608             }
609         }
610         else if (sensorType != "power")
611         {
612             properties.emplace_back(
613                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
614                 "/UpperThresholdNonCritical"_json_pointer);
615             properties.emplace_back(
616                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
617                 "/LowerThresholdNonCritical"_json_pointer);
618             properties.emplace_back(
619                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
620                 "/UpperThresholdCritical"_json_pointer);
621             properties.emplace_back(
622                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
623                 "/LowerThresholdCritical"_json_pointer);
624         }
625 
626         // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
627 
628         if (chassisSubNode == ChassisSubNode::sensorsNode)
629         {
630             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
631                                     "MinValue",
632                                     "/ReadingRangeMin"_json_pointer);
633             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
634                                     "MaxValue",
635                                     "/ReadingRangeMax"_json_pointer);
636             properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
637                                     "Accuracy", "/Accuracy"_json_pointer);
638         }
639         else if (sensorType == "temperature")
640         {
641             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
642                                     "MinValue",
643                                     "/MinReadingRangeTemp"_json_pointer);
644             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
645                                     "MaxValue",
646                                     "/MaxReadingRangeTemp"_json_pointer);
647         }
648         else if (sensorType != "power")
649         {
650             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
651                                     "MinValue",
652                                     "/MinReadingRange"_json_pointer);
653             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
654                                     "MaxValue",
655                                     "/MaxReadingRange"_json_pointer);
656         }
657     }
658 
659     for (const std::tuple<const char*, const char*,
660                           nlohmann::json::json_pointer>& p : properties)
661     {
662         for (const auto& [valueName, valueVariant] : propertiesDict)
663         {
664             if (valueName != std::get<1>(p))
665             {
666                 continue;
667             }
668 
669             // The property we want to set may be nested json, so use
670             // a json_pointer for easy indexing into the json structure.
671             const nlohmann::json::json_pointer& key = std::get<2>(p);
672 
673             const double* doubleValue = std::get_if<double>(&valueVariant);
674             if (doubleValue == nullptr)
675             {
676                 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
677                 continue;
678             }
679             if (!std::isfinite(*doubleValue))
680             {
681                 if (valueName == "Value")
682                 {
683                     // Readings are allowed to be NAN for unavailable;  coerce
684                     // them to null in the json response.
685                     sensorJson[key] = nullptr;
686                     continue;
687                 }
688                 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
689                                    valueName, *doubleValue);
690                 continue;
691             }
692             if (forceToInt)
693             {
694                 sensorJson[key] = static_cast<int64_t>(*doubleValue);
695             }
696             else
697             {
698                 sensorJson[key] = *doubleValue;
699             }
700         }
701     }
702 }
703 
704 /**
705  * @brief Builds a json sensor excerpt representation of a sensor.
706  *
707  * @details This is a wrapper function to provide consistent setting of
708  * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor
709  * excerpts usually have just the D-Bus path for the sensor that is accepted
710  * and used to build "DataSourceUri".
711 
712  * @param path The D-Bus path to the sensor to be built
713  * @param chassisId The Chassis Id for the sensor
714  * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor
715  * @param sensorTypeExpected The expected type of the sensor
716  * @param propertiesDict A dictionary of the properties to build the sensor
717  * from.
718  * @param sensorJson  The json object to fill
719  * @returns True if sensorJson object filled. False on any error.
720  * Caller is responsible for handling error.
721  */
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)722 inline bool objectExcerptToJson(
723     const std::string& path, const std::string_view chassisId,
724     ChassisSubNode chassisSubNode,
725     const std::optional<std::string>& sensorTypeExpected,
726     const dbus::utility::DBusPropertiesMap& propertiesDict,
727     nlohmann::json& sensorJson)
728 {
729     if (!isExcerptNode(chassisSubNode))
730     {
731         BMCWEB_LOG_DEBUG("{} is not a sensor excerpt",
732                          chassisSubNodeToString(chassisSubNode));
733         return false;
734     }
735 
736     sdbusplus::message::object_path sensorPath(path);
737     std::string sensorName = sensorPath.filename();
738     std::string sensorType = sensorPath.parent_path().filename();
739     if (sensorName.empty() || sensorType.empty())
740     {
741         BMCWEB_LOG_DEBUG("Invalid sensor path {}", path);
742         return false;
743     }
744 
745     if (sensorTypeExpected && (sensorType != *sensorTypeExpected))
746     {
747         BMCWEB_LOG_DEBUG("{} is not expected type {}", path,
748                          *sensorTypeExpected);
749         return false;
750     }
751 
752     // Sensor excerpts use DataSourceUri to reference full sensor Redfish path
753     sensorJson["DataSourceUri"] =
754         boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId,
755                             getSensorId(sensorName, sensorType));
756 
757     // Fill in sensor excerpt properties
758     objectPropertiesToJson(sensorName, sensorType, chassisSubNode,
759                            propertiesDict, sensorJson, nullptr);
760 
761     return true;
762 }
763 
764 // Maps D-Bus: Service, SensorPath
765 using SensorServicePathMap = std::pair<std::string, std::string>;
766 using SensorServicePathList = std::vector<SensorServicePathMap>;
767 
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)768 inline void getAllSensorObjects(
769     const std::string& associatedPath, const std::string& path,
770     std::span<const std::string_view> interfaces, const int32_t depth,
771     std::function<void(const boost::system::error_code& ec,
772                        SensorServicePathList&)>&& callback)
773 {
774     sdbusplus::message::object_path endpointPath{associatedPath};
775     endpointPath /= "all_sensors";
776 
777     dbus::utility::getAssociatedSubTree(
778         endpointPath, sdbusplus::message::object_path(path), depth, interfaces,
779         [callback = std::move(callback)](
780             const boost::system::error_code& ec,
781             const dbus::utility::MapperGetSubTreeResponse& subtree) {
782             SensorServicePathList sensorsServiceAndPath;
783 
784             if (ec)
785             {
786                 callback(ec, sensorsServiceAndPath);
787                 return;
788             }
789 
790             for (const auto& [sensorPath, serviceMaps] : subtree)
791             {
792                 for (const auto& [service, mapInterfaces] : serviceMaps)
793                 {
794                     sensorsServiceAndPath.emplace_back(service, sensorPath);
795                 }
796             }
797 
798             callback(ec, sensorsServiceAndPath);
799         });
800 }
801 
802 } // namespace sensor_utils
803 } // namespace redfish
804