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