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