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