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
dBusSensorReadingBasisToRedfish(const std::string & readingBasis)420 inline sensor::ReadingBasisType dBusSensorReadingBasisToRedfish(
421 const std::string& readingBasis)
422 {
423 if (readingBasis ==
424 "xyz.openbmc_project.Sensor.Type.ReadingBasisType.Headroom")
425 {
426 return sensor::ReadingBasisType::Headroom;
427 }
428 if (readingBasis ==
429 "xyz.openbmc_project.Sensor.Type.ReadingBasisType.Delta")
430 {
431 return sensor::ReadingBasisType::Delta;
432 }
433 if (readingBasis == "xyz.openbmc_project.Sensor.Type.ReadingBasisType.Zero")
434 {
435 return sensor::ReadingBasisType::Zero;
436 }
437
438 return sensor::ReadingBasisType::Invalid;
439 }
440
dBusSensorImplementationToRedfish(const std::string & implementation)441 inline sensor::ImplementationType dBusSensorImplementationToRedfish(
442 const std::string& implementation)
443 {
444 if (implementation ==
445 "xyz.openbmc_project.Sensor.Type.ImplementationType.Physical")
446 {
447 return sensor::ImplementationType::PhysicalSensor;
448 }
449 if (implementation ==
450 "xyz.openbmc_project.Sensor.Type.ImplementationType.Synthesized")
451 {
452 return sensor::ImplementationType::Synthesized;
453 }
454 if (implementation ==
455 "xyz.openbmc_project.Sensor.Type.ImplementationType.Reported")
456 {
457 return sensor::ImplementationType::Reported;
458 }
459
460 return sensor::ImplementationType::Invalid;
461 }
462
463 /**
464 * @brief Builds a json sensor representation of a sensor.
465 * @param sensorName The name of the sensor to be built
466 * @param sensorType The type (temperature, fan_tach, etc) of the sensor to
467 * build
468 * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
469 * @param propertiesDict A dictionary of the properties to build the sensor
470 * from.
471 * @param sensorJson The json object to fill
472 * @param inventoryItem D-Bus inventory item associated with the sensor. Will
473 * be nullptr if no associated inventory item was found.
474 */
objectPropertiesToJson(std::string_view sensorName,std::string_view sensorType,ChassisSubNode chassisSubNode,const dbus::utility::DBusPropertiesMap & propertiesDict,nlohmann::json & sensorJson,InventoryItem * inventoryItem)475 inline void objectPropertiesToJson(
476 std::string_view sensorName, std::string_view sensorType,
477 ChassisSubNode chassisSubNode,
478 const dbus::utility::DBusPropertiesMap& propertiesDict,
479 nlohmann::json& sensorJson, InventoryItem* inventoryItem)
480 {
481 // Parameter to set to override the type we get from dbus, and force it to
482 // int, regardless of what is available. This is used for schemas like fan,
483 // that require integers, not floats.
484 bool forceToInt = false;
485
486 nlohmann::json::json_pointer unit("/Reading");
487
488 // This ChassisSubNode builds sensor excerpts
489 bool isExcerpt = isExcerptNode(chassisSubNode);
490
491 /* Sensor excerpts use different keys to reference the sensor. These are
492 * built by the caller.
493 * Additionally they don't include these additional properties.
494 */
495 if (!isExcerpt)
496 {
497 if (chassisSubNode == ChassisSubNode::sensorsNode)
498 {
499 std::string subNodeEscaped = getSensorId(sensorName, sensorType);
500 // For sensors in SensorCollection we set Id instead of MemberId,
501 // including power sensors.
502 sensorJson["Id"] = std::move(subNodeEscaped);
503
504 std::string sensorNameEs(sensorName);
505 std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
506 sensorJson["Name"] = std::move(sensorNameEs);
507 }
508 else if (sensorType != "power")
509 {
510 // Set MemberId and Name for non-power sensors. For PowerSupplies
511 // and PowerControl, those properties have more general values
512 // because multiple sensors can be stored in the same JSON object.
513 std::string sensorNameEs(sensorName);
514 std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
515 sensorJson["Name"] = std::move(sensorNameEs);
516 }
517
518 const bool* checkAvailable = nullptr;
519 bool available = true;
520 std::optional<std::string> readingBasis;
521 std::optional<std::string> implementation;
522
523 const bool success = sdbusplus::unpackPropertiesNoThrow(
524 dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available",
525 checkAvailable, "ReadingBasis", readingBasis, "Implementation",
526 implementation);
527 if (!success)
528 {
529 messages::internalError();
530 }
531 if (checkAvailable != nullptr)
532 {
533 available = *checkAvailable;
534 }
535
536 sensorJson["Status"]["State"] = getState(inventoryItem, available);
537 sensorJson["Status"]["Health"] =
538 getHealth(sensorJson, propertiesDict, inventoryItem);
539
540 if (chassisSubNode == ChassisSubNode::sensorsNode)
541 {
542 sensorJson["@odata.type"] = "#Sensor.v1_11_0.Sensor";
543
544 sensor::ReadingType readingType =
545 sensors::toReadingType(sensorType);
546 if (readingType == sensor::ReadingType::Invalid)
547 {
548 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}",
549 sensorType);
550 }
551 else
552 {
553 sensorJson["ReadingType"] = readingType;
554 }
555
556 std::string_view readingUnits = sensors::toReadingUnits(sensorType);
557 if (readingUnits.empty())
558 {
559 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}",
560 sensorType);
561 }
562 else
563 {
564 sensorJson["ReadingUnits"] = readingUnits;
565 }
566
567 if (readingBasis.has_value())
568 {
569 sensor::ReadingBasisType readingBasisOpt =
570 dBusSensorReadingBasisToRedfish(*readingBasis);
571 if (readingBasisOpt != sensor::ReadingBasisType::Invalid)
572 {
573 sensorJson["ReadingBasis"] = readingBasisOpt;
574 }
575 }
576
577 if (implementation.has_value())
578 {
579 sensor::ImplementationType implementationOpt =
580 dBusSensorImplementationToRedfish(*implementation);
581 if (implementationOpt != sensor::ImplementationType::Invalid)
582 {
583 sensorJson["Implementation"] = implementationOpt;
584 }
585 }
586 }
587 else if (sensorType == "temperature")
588 {
589 unit = "/ReadingCelsius"_json_pointer;
590 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature";
591 // TODO(ed) Documentation says that path should be type fan_tach,
592 // implementation seems to implement fan
593 }
594 else if (sensorType == "fan" || sensorType == "fan_tach")
595 {
596 unit = "/Reading"_json_pointer;
597 sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM;
598 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
599 if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_INDICATORLED)
600 {
601 setLedState(sensorJson, inventoryItem);
602 }
603 forceToInt = true;
604 }
605 else if (sensorType == "fan_pwm")
606 {
607 unit = "/Reading"_json_pointer;
608 sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent;
609 sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
610 if constexpr (BMCWEB_REDFISH_ALLOW_DEPRECATED_INDICATORLED)
611 {
612 setLedState(sensorJson, inventoryItem);
613 }
614 forceToInt = true;
615 }
616 else if (sensorType == "voltage")
617 {
618 unit = "/ReadingVolts"_json_pointer;
619 sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
620 }
621 else if (sensorType == "power")
622 {
623 std::string lower;
624 std::ranges::transform(sensorName, std::back_inserter(lower),
625 bmcweb::asciiToLower);
626 if (lower == "total_power")
627 {
628 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
629 // Put multiple "sensors" into a single PowerControl, so have
630 // generic names for MemberId and Name. Follows Redfish mockup.
631 sensorJson["MemberId"] = "0";
632 sensorJson["Name"] = "Chassis Power Control";
633 unit = "/PowerConsumedWatts"_json_pointer;
634 }
635 else if (lower.find("input") != std::string::npos)
636 {
637 unit = "/PowerInputWatts"_json_pointer;
638 }
639 else
640 {
641 unit = "/PowerOutputWatts"_json_pointer;
642 }
643 }
644 else
645 {
646 BMCWEB_LOG_ERROR("Redfish cannot map object type for {}",
647 sensorName);
648 return;
649 }
650 }
651
652 // Map of dbus interface name, dbus property name and redfish property_name
653 std::vector<
654 std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
655 properties;
656
657 properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
658
659 if (!isExcerpt)
660 {
661 if (chassisSubNode == ChassisSubNode::sensorsNode)
662 {
663 properties.emplace_back(
664 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
665 "/Thresholds/UpperCaution/Reading"_json_pointer);
666 properties.emplace_back(
667 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
668 "/Thresholds/LowerCaution/Reading"_json_pointer);
669 properties.emplace_back(
670 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
671 "/Thresholds/UpperCritical/Reading"_json_pointer);
672 properties.emplace_back(
673 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
674 "/Thresholds/LowerCritical/Reading"_json_pointer);
675 properties.emplace_back(
676 "xyz.openbmc_project.Sensor.Threshold.HardShutdown",
677 "HardShutdownHigh",
678 "/Thresholds/UpperFatal/Reading"_json_pointer);
679 properties.emplace_back(
680 "xyz.openbmc_project.Sensor.Threshold.HardShutdown",
681 "HardShutdownLow",
682 "/Thresholds/LowerFatal/Reading"_json_pointer);
683
684 /* Add additional properties specific to sensorType */
685 if (sensorType == "fan_tach")
686 {
687 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
688 "Value", "/SpeedRPM"_json_pointer);
689 }
690 }
691 else if (sensorType != "power")
692 {
693 properties.emplace_back(
694 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
695 "/UpperThresholdNonCritical"_json_pointer);
696 properties.emplace_back(
697 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
698 "/LowerThresholdNonCritical"_json_pointer);
699 properties.emplace_back(
700 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
701 "/UpperThresholdCritical"_json_pointer);
702 properties.emplace_back(
703 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
704 "/LowerThresholdCritical"_json_pointer);
705 }
706
707 // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
708
709 if (chassisSubNode == ChassisSubNode::sensorsNode)
710 {
711 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
712 "MinValue",
713 "/ReadingRangeMin"_json_pointer);
714 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
715 "MaxValue",
716 "/ReadingRangeMax"_json_pointer);
717 properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
718 "Accuracy", "/Accuracy"_json_pointer);
719 }
720 else if (sensorType == "temperature")
721 {
722 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
723 "MinValue",
724 "/MinReadingRangeTemp"_json_pointer);
725 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
726 "MaxValue",
727 "/MaxReadingRangeTemp"_json_pointer);
728 }
729 else if (sensorType != "power")
730 {
731 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
732 "MinValue",
733 "/MinReadingRange"_json_pointer);
734 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
735 "MaxValue",
736 "/MaxReadingRange"_json_pointer);
737 }
738 }
739
740 for (const std::tuple<const char*, const char*,
741 nlohmann::json::json_pointer>& p : properties)
742 {
743 for (const auto& [valueName, valueVariant] : propertiesDict)
744 {
745 if (valueName != std::get<1>(p))
746 {
747 continue;
748 }
749
750 // The property we want to set may be nested json, so use
751 // a json_pointer for easy indexing into the json structure.
752 const nlohmann::json::json_pointer& key = std::get<2>(p);
753
754 const double* doubleValue = std::get_if<double>(&valueVariant);
755 if (doubleValue == nullptr)
756 {
757 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
758 continue;
759 }
760 if (!std::isfinite(*doubleValue))
761 {
762 if (valueName == "Value")
763 {
764 // Readings are allowed to be NAN for unavailable; coerce
765 // them to null in the json response.
766 sensorJson[key] = nullptr;
767 continue;
768 }
769 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
770 valueName, *doubleValue);
771 continue;
772 }
773 if (forceToInt)
774 {
775 sensorJson[key] = static_cast<int64_t>(*doubleValue);
776 }
777 else
778 {
779 sensorJson[key] = *doubleValue;
780 }
781 }
782 }
783 }
784
785 /**
786 * @brief Builds a json sensor excerpt representation of a sensor.
787 *
788 * @details This is a wrapper function to provide consistent setting of
789 * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor
790 * excerpts usually have just the D-Bus path for the sensor that is accepted
791 * and used to build "DataSourceUri".
792
793 * @param path The D-Bus path to the sensor to be built
794 * @param chassisId The Chassis Id for the sensor
795 * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor
796 * @param sensorTypeExpected The expected type of the sensor
797 * @param propertiesDict A dictionary of the properties to build the sensor
798 * from.
799 * @param sensorJson The json object to fill
800 * @returns True if sensorJson object filled. False on any error.
801 * Caller is responsible for handling error.
802 */
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)803 inline bool objectExcerptToJson(
804 const std::string& path, const std::string_view chassisId,
805 ChassisSubNode chassisSubNode,
806 const std::optional<std::string>& sensorTypeExpected,
807 const dbus::utility::DBusPropertiesMap& propertiesDict,
808 nlohmann::json& sensorJson)
809 {
810 if (!isExcerptNode(chassisSubNode))
811 {
812 BMCWEB_LOG_DEBUG("{} is not a sensor excerpt",
813 chassisSubNodeToString(chassisSubNode));
814 return false;
815 }
816
817 sdbusplus::message::object_path sensorPath(path);
818 std::string sensorName = sensorPath.filename();
819 std::string sensorType = sensorPath.parent_path().filename();
820 if (sensorName.empty() || sensorType.empty())
821 {
822 BMCWEB_LOG_DEBUG("Invalid sensor path {}", path);
823 return false;
824 }
825
826 if (sensorTypeExpected && (sensorType != *sensorTypeExpected))
827 {
828 BMCWEB_LOG_DEBUG("{} is not expected type {}", path,
829 *sensorTypeExpected);
830 return false;
831 }
832
833 // Sensor excerpts use DataSourceUri to reference full sensor Redfish path
834 sensorJson["DataSourceUri"] =
835 boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId,
836 getSensorId(sensorName, sensorType));
837
838 // Fill in sensor excerpt properties
839 objectPropertiesToJson(sensorName, sensorType, chassisSubNode,
840 propertiesDict, sensorJson, nullptr);
841
842 return true;
843 }
844
845 // Maps D-Bus: Service, SensorPath
846 using SensorServicePathMap = std::pair<std::string, std::string>;
847 using SensorServicePathList = std::vector<SensorServicePathMap>;
848
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)849 inline void getAllSensorObjects(
850 const std::string& associatedPath, const std::string& path,
851 std::span<const std::string_view> interfaces, const int32_t depth,
852 std::function<void(const boost::system::error_code& ec,
853 SensorServicePathList&)>&& callback)
854 {
855 sdbusplus::message::object_path endpointPath{associatedPath};
856 endpointPath /= "all_sensors";
857
858 dbus::utility::getAssociatedSubTree(
859 endpointPath, sdbusplus::message::object_path(path), depth, interfaces,
860 [callback = std::move(callback)](
861 const boost::system::error_code& ec,
862 const dbus::utility::MapperGetSubTreeResponse& subtree) {
863 SensorServicePathList sensorsServiceAndPath;
864
865 if (ec)
866 {
867 callback(ec, sensorsServiceAndPath);
868 return;
869 }
870
871 for (const auto& [sensorPath, serviceMaps] : subtree)
872 {
873 for (const auto& [service, mapInterfaces] : serviceMaps)
874 {
875 sensorsServiceAndPath.emplace_back(service, sensorPath);
876 }
877 }
878
879 callback(ec, sensorsServiceAndPath);
880 });
881 }
882
883 enum class SensorPurpose
884 {
885 totalPower,
886 unknownPurpose,
887 };
888
sensorPurposeToString(SensorPurpose subNode)889 constexpr std::string_view sensorPurposeToString(SensorPurpose subNode)
890 {
891 if (subNode == SensorPurpose::totalPower)
892 {
893 return "xyz.openbmc_project.Sensor.Purpose.SensorPurpose.TotalPower";
894 }
895
896 return "Unknown Purpose";
897 }
898
sensorPurposeFromString(const std::string & subNodeStr)899 inline SensorPurpose sensorPurposeFromString(const std::string& subNodeStr)
900 {
901 // If none match unknownNode is returned
902 SensorPurpose subNode = SensorPurpose::unknownPurpose;
903
904 if (subNodeStr ==
905 "xyz.openbmc_project.Sensor.Purpose.SensorPurpose.TotalPower")
906 {
907 subNode = SensorPurpose::totalPower;
908 }
909
910 return subNode;
911 }
912
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)913 inline void checkSensorPurpose(
914 const std::string& serviceName, const std::string& sensorPath,
915 const SensorPurpose sensorPurpose,
916 const std::shared_ptr<SensorServicePathList>& sensorMatches,
917 const std::shared_ptr<boost::system::error_code>& asyncErrors,
918 const boost::system::error_code& ec,
919 const std::vector<std::string>& purposeList)
920 {
921 // If not found this sensor will be skipped but allow to
922 // continue processing remaining sensors
923 if (ec && (ec != boost::system::errc::io_error) && (ec.value() != EBADR))
924 {
925 BMCWEB_LOG_DEBUG("D-Bus response error : {}", ec);
926 *asyncErrors = ec;
927 }
928
929 if (!ec)
930 {
931 const std::string_view checkPurposeStr =
932 sensorPurposeToString(sensorPurpose);
933
934 BMCWEB_LOG_DEBUG("checkSensorPurpose: {}", checkPurposeStr);
935
936 for (const std::string& purposeStr : purposeList)
937 {
938 if (purposeStr == checkPurposeStr)
939 {
940 // Add to list
941 BMCWEB_LOG_DEBUG("checkSensorPurpose adding {} {}", serviceName,
942 sensorPath);
943 sensorMatches->emplace_back(serviceName, sensorPath);
944 return;
945 }
946 }
947 }
948 }
949
950 /**
951 * @brief Gets sensors from list with specified purpose
952 *
953 * Checks the <sensorListIn> sensors for any which implement the specified
954 * <sensorPurpose> in interface xyz.openbmc_project.Sensor.Purpose. Adds any
955 * matches to the <sensorMatches> list. After checking all sensors in
956 * <sensorListIn> the <callback> is called with just the list of matching
957 * sensors, which could be an empty list. Additionally the <callback> is called
958 * on error and error handling is left to the callback.
959 *
960 * @param asyncResp Response data
961 * @param[in] sensorListIn List of sensors to check
962 * @param[in] sensorPurpose Looks for sensors matching this purpose
963 * @param[out] sensorMatches Sensors from sensorListIn with sensorPurpose
964 * @param[in] callback Callback to handle list of matching sensors
965 */
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)966 inline void getSensorsByPurpose(
967 const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
968 const SensorServicePathList& sensorListIn,
969 const SensorPurpose sensorPurpose,
970 const std::shared_ptr<SensorServicePathList>& sensorMatches,
971 const std::function<void(
972 const boost::system::error_code& ec,
973 const std::shared_ptr<SensorServicePathList>& sensorMatches)>& callback)
974 {
975 /* Keeps track of number of asynchronous calls made. Once all handlers have
976 * been called the callback function is called with the results.
977 */
978 std::shared_ptr<int> remainingSensorsToVist =
979 std::make_shared<int>(sensorListIn.size());
980
981 /* Holds last unrecoverable error returned by any of the asynchronous
982 * handlers. Once all handlers have been called the callback will be sent
983 * the error to handle.
984 */
985 std::shared_ptr<boost::system::error_code> asyncErrors =
986 std::make_shared<boost::system::error_code>();
987
988 BMCWEB_LOG_DEBUG("getSensorsByPurpose enter {}", *remainingSensorsToVist);
989
990 for (const auto& [serviceName, sensorPath] : sensorListIn)
991 {
992 dbus::utility::getProperty<std::vector<std::string>>(
993 serviceName, sensorPath, "xyz.openbmc_project.Sensor.Purpose",
994 "Purpose",
995 [asyncResp, serviceName, sensorPath, sensorPurpose, sensorMatches,
996 callback, remainingSensorsToVist,
997 asyncErrors](const boost::system::error_code& ec,
998 const std::vector<std::string>& purposeList) {
999 // Keep track of sensor visited
1000 (*remainingSensorsToVist)--;
1001 BMCWEB_LOG_DEBUG("Visited {}. Remaining sensors {}", sensorPath,
1002 *remainingSensorsToVist);
1003
1004 checkSensorPurpose(serviceName, sensorPath, sensorPurpose,
1005 sensorMatches, asyncErrors, ec, purposeList);
1006
1007 // All sensors have been visited
1008 if (*remainingSensorsToVist == 0)
1009 {
1010 BMCWEB_LOG_DEBUG(
1011 "getSensorsByPurpose, exit found {} matches",
1012 sensorMatches->size());
1013 callback(*asyncErrors, sensorMatches);
1014 return;
1015 }
1016 });
1017 }
1018 }
1019
1020 } // namespace sensor_utils
1021 } // namespace redfish
1022