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