xref: /openbmc/bmcweb/redfish-core/include/utils/sensor_utils.hpp (revision 4e8d8a1ce8941a47f0531f2af565c56a5d604119)
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