xref: /openbmc/bmcweb/redfish-core/include/utils/sensor_utils.hpp (revision 4491419217d426791003facc749f600692d9b2a6)
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             setLedState(sensorJson, inventoryItem);
519             forceToInt = true;
520         }
521         else if (sensorType == "fan_pwm")
522         {
523             unit = "/Reading"_json_pointer;
524             sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent;
525             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
526             setLedState(sensorJson, inventoryItem);
527             forceToInt = true;
528         }
529         else if (sensorType == "voltage")
530         {
531             unit = "/ReadingVolts"_json_pointer;
532             sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
533         }
534         else if (sensorType == "power")
535         {
536             std::string lower;
537             std::ranges::transform(sensorName, std::back_inserter(lower),
538                                    bmcweb::asciiToLower);
539             if (lower == "total_power")
540             {
541                 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
542                 // Put multiple "sensors" into a single PowerControl, so have
543                 // generic names for MemberId and Name. Follows Redfish mockup.
544                 sensorJson["MemberId"] = "0";
545                 sensorJson["Name"] = "Chassis Power Control";
546                 unit = "/PowerConsumedWatts"_json_pointer;
547             }
548             else if (lower.find("input") != std::string::npos)
549             {
550                 unit = "/PowerInputWatts"_json_pointer;
551             }
552             else
553             {
554                 unit = "/PowerOutputWatts"_json_pointer;
555             }
556         }
557         else
558         {
559             BMCWEB_LOG_ERROR("Redfish cannot map object type for {}",
560                              sensorName);
561             return;
562         }
563     }
564 
565     // Map of dbus interface name, dbus property name and redfish property_name
566     std::vector<
567         std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
568         properties;
569 
570     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
571 
572     if (!isExcerpt)
573     {
574         if (chassisSubNode == ChassisSubNode::sensorsNode)
575         {
576             properties.emplace_back(
577                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
578                 "/Thresholds/UpperCaution/Reading"_json_pointer);
579             properties.emplace_back(
580                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
581                 "/Thresholds/LowerCaution/Reading"_json_pointer);
582             properties.emplace_back(
583                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
584                 "/Thresholds/UpperCritical/Reading"_json_pointer);
585             properties.emplace_back(
586                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
587                 "/Thresholds/LowerCritical/Reading"_json_pointer);
588 
589             /* Add additional properties specific to sensorType */
590             if (sensorType == "fan_tach")
591             {
592                 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
593                                         "Value", "/SpeedRPM"_json_pointer);
594             }
595         }
596         else if (sensorType != "power")
597         {
598             properties.emplace_back(
599                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
600                 "/UpperThresholdNonCritical"_json_pointer);
601             properties.emplace_back(
602                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
603                 "/LowerThresholdNonCritical"_json_pointer);
604             properties.emplace_back(
605                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
606                 "/UpperThresholdCritical"_json_pointer);
607             properties.emplace_back(
608                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
609                 "/LowerThresholdCritical"_json_pointer);
610         }
611 
612         // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
613 
614         if (chassisSubNode == ChassisSubNode::sensorsNode)
615         {
616             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
617                                     "MinValue",
618                                     "/ReadingRangeMin"_json_pointer);
619             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
620                                     "MaxValue",
621                                     "/ReadingRangeMax"_json_pointer);
622             properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
623                                     "Accuracy", "/Accuracy"_json_pointer);
624         }
625         else if (sensorType == "temperature")
626         {
627             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
628                                     "MinValue",
629                                     "/MinReadingRangeTemp"_json_pointer);
630             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
631                                     "MaxValue",
632                                     "/MaxReadingRangeTemp"_json_pointer);
633         }
634         else if (sensorType != "power")
635         {
636             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
637                                     "MinValue",
638                                     "/MinReadingRange"_json_pointer);
639             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
640                                     "MaxValue",
641                                     "/MaxReadingRange"_json_pointer);
642         }
643     }
644 
645     for (const std::tuple<const char*, const char*,
646                           nlohmann::json::json_pointer>& p : properties)
647     {
648         for (const auto& [valueName, valueVariant] : propertiesDict)
649         {
650             if (valueName != std::get<1>(p))
651             {
652                 continue;
653             }
654 
655             // The property we want to set may be nested json, so use
656             // a json_pointer for easy indexing into the json structure.
657             const nlohmann::json::json_pointer& key = std::get<2>(p);
658 
659             const double* doubleValue = std::get_if<double>(&valueVariant);
660             if (doubleValue == nullptr)
661             {
662                 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
663                 continue;
664             }
665             if (!std::isfinite(*doubleValue))
666             {
667                 if (valueName == "Value")
668                 {
669                     // Readings are allowed to be NAN for unavailable;  coerce
670                     // them to null in the json response.
671                     sensorJson[key] = nullptr;
672                     continue;
673                 }
674                 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
675                                    valueName, *doubleValue);
676                 continue;
677             }
678             if (forceToInt)
679             {
680                 sensorJson[key] = static_cast<int64_t>(*doubleValue);
681             }
682             else
683             {
684                 sensorJson[key] = *doubleValue;
685             }
686         }
687     }
688 }
689 
690 /**
691  * @brief Builds a json sensor excerpt representation of a sensor.
692  *
693  * @details This is a wrapper function to provide consistent setting of
694  * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor
695  * excerpts usually have just the D-Bus path for the sensor that is accepted
696  * and used to build "DataSourceUri".
697 
698  * @param path The D-Bus path to the sensor to be built
699  * @param chassisId The Chassis Id for the sensor
700  * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor
701  * @param sensorTypeExpected The expected type of the sensor
702  * @param propertiesDict A dictionary of the properties to build the sensor
703  * from.
704  * @param sensorJson  The json object to fill
705  * @returns True if sensorJson object filled. False on any error.
706  * Caller is responsible for handling error.
707  */
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)708 inline bool objectExcerptToJson(
709     const std::string& path, const std::string_view chassisId,
710     ChassisSubNode chassisSubNode,
711     const std::optional<std::string>& sensorTypeExpected,
712     const dbus::utility::DBusPropertiesMap& propertiesDict,
713     nlohmann::json& sensorJson)
714 {
715     if (!isExcerptNode(chassisSubNode))
716     {
717         BMCWEB_LOG_DEBUG("{} is not a sensor excerpt",
718                          chassisSubNodeToString(chassisSubNode));
719         return false;
720     }
721 
722     sdbusplus::message::object_path sensorPath(path);
723     std::string sensorName = sensorPath.filename();
724     std::string sensorType = sensorPath.parent_path().filename();
725     if (sensorName.empty() || sensorType.empty())
726     {
727         BMCWEB_LOG_DEBUG("Invalid sensor path {}", path);
728         return false;
729     }
730 
731     if (sensorTypeExpected && (sensorType != *sensorTypeExpected))
732     {
733         BMCWEB_LOG_DEBUG("{} is not expected type {}", path,
734                          *sensorTypeExpected);
735         return false;
736     }
737 
738     // Sensor excerpts use DataSourceUri to reference full sensor Redfish path
739     sensorJson["DataSourceUri"] =
740         boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId,
741                             getSensorId(sensorName, sensorType));
742 
743     // Fill in sensor excerpt properties
744     objectPropertiesToJson(sensorName, sensorType, chassisSubNode,
745                            propertiesDict, sensorJson, nullptr);
746 
747     return true;
748 }
749 
750 // Maps D-Bus: Service, SensorPath
751 using SensorServicePathMap = std::pair<std::string, std::string>;
752 using SensorServicePathList = std::vector<SensorServicePathMap>;
753 
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)754 inline void getAllSensorObjects(
755     const std::string& associatedPath, const std::string& path,
756     std::span<const std::string_view> interfaces, const int32_t depth,
757     std::function<void(const boost::system::error_code& ec,
758                        SensorServicePathList&)>&& callback)
759 {
760     sdbusplus::message::object_path endpointPath{associatedPath};
761     endpointPath /= "all_sensors";
762 
763     dbus::utility::getAssociatedSubTree(
764         endpointPath, sdbusplus::message::object_path(path), depth, interfaces,
765         [callback = std::move(callback)](
766             const boost::system::error_code& ec,
767             const dbus::utility::MapperGetSubTreeResponse& subtree) {
768             SensorServicePathList sensorsServiceAndPath;
769 
770             if (ec)
771             {
772                 callback(ec, sensorsServiceAndPath);
773                 return;
774             }
775 
776             for (const auto& [sensorPath, serviceMaps] : subtree)
777             {
778                 for (const auto& [service, mapInterfaces] : serviceMaps)
779                 {
780                     sensorsServiceAndPath.emplace_back(service, sensorPath);
781                 }
782             }
783 
784             callback(ec, sensorsServiceAndPath);
785         });
786 }
787 
788 } // namespace sensor_utils
789 } // namespace redfish
790