xref: /openbmc/bmcweb/features/redfish/include/utils/sensor_utils.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1*40e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
2*40e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
31516c21bSJanet Adkins #pragma once
41516c21bSJanet Adkins 
5c9563608SJanet Adkins #include "dbus_utility.hpp"
6c9563608SJanet Adkins #include "generated/enums/resource.hpp"
7c9563608SJanet Adkins #include "generated/enums/sensor.hpp"
8c9563608SJanet Adkins #include "generated/enums/thermal.hpp"
9c9563608SJanet Adkins #include "str_utility.hpp"
10c9563608SJanet Adkins #include "utils/dbus_utils.hpp"
11c9563608SJanet Adkins #include "utils/json_utils.hpp"
12c9563608SJanet Adkins 
136fe8751cSGeorge Liu #include <boost/url/format.hpp>
14c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp>
15c9563608SJanet Adkins 
161516c21bSJanet Adkins #include <algorithm>
171516c21bSJanet Adkins #include <format>
186fe8751cSGeorge Liu #include <functional>
196fe8751cSGeorge Liu #include <optional>
201516c21bSJanet Adkins #include <ranges>
211516c21bSJanet Adkins #include <string>
221516c21bSJanet Adkins #include <string_view>
23c9563608SJanet Adkins #include <tuple>
241516c21bSJanet Adkins #include <utility>
251516c21bSJanet Adkins #include <vector>
261516c21bSJanet Adkins 
271516c21bSJanet Adkins namespace redfish
281516c21bSJanet Adkins {
291516c21bSJanet Adkins namespace sensor_utils
301516c21bSJanet Adkins {
311516c21bSJanet Adkins 
320c728b42SJanet Adkins enum class ChassisSubNode
330c728b42SJanet Adkins {
340c728b42SJanet Adkins     powerNode,
350c728b42SJanet Adkins     sensorsNode,
360c728b42SJanet Adkins     thermalNode,
376fe8751cSGeorge Liu     thermalMetricsNode,
380c728b42SJanet Adkins     unknownNode,
390c728b42SJanet Adkins };
400c728b42SJanet Adkins 
410c728b42SJanet Adkins constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode)
420c728b42SJanet Adkins {
430c728b42SJanet Adkins     switch (subNode)
440c728b42SJanet Adkins     {
450c728b42SJanet Adkins         case ChassisSubNode::powerNode:
460c728b42SJanet Adkins             return "Power";
470c728b42SJanet Adkins         case ChassisSubNode::sensorsNode:
480c728b42SJanet Adkins             return "Sensors";
490c728b42SJanet Adkins         case ChassisSubNode::thermalNode:
500c728b42SJanet Adkins             return "Thermal";
516fe8751cSGeorge Liu         case ChassisSubNode::thermalMetricsNode:
526fe8751cSGeorge Liu             return "ThermalMetrics";
530c728b42SJanet Adkins         case ChassisSubNode::unknownNode:
540c728b42SJanet Adkins         default:
550c728b42SJanet Adkins             return "";
560c728b42SJanet Adkins     }
570c728b42SJanet Adkins }
580c728b42SJanet Adkins 
590c728b42SJanet Adkins inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr)
600c728b42SJanet Adkins {
610c728b42SJanet Adkins     // If none match unknownNode is returned
620c728b42SJanet Adkins     ChassisSubNode subNode = ChassisSubNode::unknownNode;
630c728b42SJanet Adkins 
640c728b42SJanet Adkins     if (subNodeStr == "Power")
650c728b42SJanet Adkins     {
660c728b42SJanet Adkins         subNode = ChassisSubNode::powerNode;
670c728b42SJanet Adkins     }
680c728b42SJanet Adkins     else if (subNodeStr == "Sensors")
690c728b42SJanet Adkins     {
700c728b42SJanet Adkins         subNode = ChassisSubNode::sensorsNode;
710c728b42SJanet Adkins     }
720c728b42SJanet Adkins     else if (subNodeStr == "Thermal")
730c728b42SJanet Adkins     {
740c728b42SJanet Adkins         subNode = ChassisSubNode::thermalNode;
750c728b42SJanet Adkins     }
766fe8751cSGeorge Liu     else if (subNodeStr == "ThermalMetrics")
776fe8751cSGeorge Liu     {
786fe8751cSGeorge Liu         subNode = ChassisSubNode::thermalMetricsNode;
796fe8751cSGeorge Liu     }
800c728b42SJanet Adkins 
810c728b42SJanet Adkins     return subNode;
820c728b42SJanet Adkins }
83c9563608SJanet Adkins 
846fe8751cSGeorge Liu inline bool isExcerptNode(const ChassisSubNode subNode)
856fe8751cSGeorge Liu {
866fe8751cSGeorge Liu     return (subNode == ChassisSubNode::thermalMetricsNode);
876fe8751cSGeorge Liu }
886fe8751cSGeorge Liu 
89c9563608SJanet Adkins /**
90c9563608SJanet Adkins  * Possible states for physical inventory leds
91c9563608SJanet Adkins  */
92c9563608SJanet Adkins enum class LedState
93c9563608SJanet Adkins {
94c9563608SJanet Adkins     OFF,
95c9563608SJanet Adkins     ON,
96c9563608SJanet Adkins     BLINK,
97c9563608SJanet Adkins     UNKNOWN
98c9563608SJanet Adkins };
99c9563608SJanet Adkins 
100c9563608SJanet Adkins /**
101c9563608SJanet Adkins  * D-Bus inventory item associated with one or more sensors.
102c9563608SJanet Adkins  */
103c9563608SJanet Adkins class InventoryItem
104c9563608SJanet Adkins {
105c9563608SJanet Adkins   public:
106c9563608SJanet Adkins     explicit InventoryItem(const std::string& objPath) : objectPath(objPath)
107c9563608SJanet Adkins     {
108c9563608SJanet Adkins         // Set inventory item name to last node of object path
109c9563608SJanet Adkins         sdbusplus::message::object_path path(objectPath);
110c9563608SJanet Adkins         name = path.filename();
111c9563608SJanet Adkins         if (name.empty())
112c9563608SJanet Adkins         {
113c9563608SJanet Adkins             BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath);
114c9563608SJanet Adkins         }
115c9563608SJanet Adkins     }
116c9563608SJanet Adkins 
117c9563608SJanet Adkins     std::string objectPath;
118c9563608SJanet Adkins     std::string name;
119c9563608SJanet Adkins     bool isPresent = true;
120c9563608SJanet Adkins     bool isFunctional = true;
121c9563608SJanet Adkins     bool isPowerSupply = false;
122c9563608SJanet Adkins     int powerSupplyEfficiencyPercent = -1;
123c9563608SJanet Adkins     std::string manufacturer;
124c9563608SJanet Adkins     std::string model;
125c9563608SJanet Adkins     std::string partNumber;
126c9563608SJanet Adkins     std::string serialNumber;
127c9563608SJanet Adkins     std::set<std::string> sensors;
128c9563608SJanet Adkins     std::string ledObjectPath;
129c9563608SJanet Adkins     LedState ledState = LedState::UNKNOWN;
130c9563608SJanet Adkins };
131c9563608SJanet Adkins 
1321516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName,
1331516c21bSJanet Adkins                                std::string_view sensorType)
1341516c21bSJanet Adkins {
1351516c21bSJanet Adkins     std::string normalizedType(sensorType);
1361516c21bSJanet Adkins     auto remove = std::ranges::remove(normalizedType, '_');
1371516c21bSJanet Adkins     normalizedType.erase(std::ranges::begin(remove), normalizedType.end());
1381516c21bSJanet Adkins 
1391516c21bSJanet Adkins     return std::format("{}_{}", normalizedType, sensorName);
1401516c21bSJanet Adkins }
1411516c21bSJanet Adkins 
1421516c21bSJanet Adkins inline std::pair<std::string, std::string>
1431516c21bSJanet Adkins     splitSensorNameAndType(std::string_view sensorId)
1441516c21bSJanet Adkins {
1451516c21bSJanet Adkins     size_t index = sensorId.find('_');
1461516c21bSJanet Adkins     if (index == std::string::npos)
1471516c21bSJanet Adkins     {
1481516c21bSJanet Adkins         return std::make_pair<std::string, std::string>("", "");
1491516c21bSJanet Adkins     }
1501516c21bSJanet Adkins     std::string sensorType{sensorId.substr(0, index)};
1511516c21bSJanet Adkins     std::string sensorName{sensorId.substr(index + 1)};
1521516c21bSJanet Adkins     // fan_pwm and fan_tach need special handling
1531516c21bSJanet Adkins     if (sensorType == "fantach" || sensorType == "fanpwm")
1541516c21bSJanet Adkins     {
1551516c21bSJanet Adkins         sensorType.insert(3, 1, '_');
1561516c21bSJanet Adkins     }
1571516c21bSJanet Adkins     return std::make_pair(sensorType, sensorName);
1581516c21bSJanet Adkins }
1591516c21bSJanet Adkins 
160c9563608SJanet Adkins namespace sensors
161c9563608SJanet Adkins {
162c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType)
163c9563608SJanet Adkins {
164c9563608SJanet Adkins     if (sensorType == "voltage")
165c9563608SJanet Adkins     {
166c9563608SJanet Adkins         return "V";
167c9563608SJanet Adkins     }
168c9563608SJanet Adkins     if (sensorType == "power")
169c9563608SJanet Adkins     {
170c9563608SJanet Adkins         return "W";
171c9563608SJanet Adkins     }
172c9563608SJanet Adkins     if (sensorType == "current")
173c9563608SJanet Adkins     {
174c9563608SJanet Adkins         return "A";
175c9563608SJanet Adkins     }
176c9563608SJanet Adkins     if (sensorType == "fan_tach")
177c9563608SJanet Adkins     {
178c9563608SJanet Adkins         return "RPM";
179c9563608SJanet Adkins     }
180c9563608SJanet Adkins     if (sensorType == "temperature")
181c9563608SJanet Adkins     {
182c9563608SJanet Adkins         return "Cel";
183c9563608SJanet Adkins     }
184c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization" ||
185c9563608SJanet Adkins         sensorType == "humidity")
186c9563608SJanet Adkins     {
187c9563608SJanet Adkins         return "%";
188c9563608SJanet Adkins     }
189c9563608SJanet Adkins     if (sensorType == "altitude")
190c9563608SJanet Adkins     {
191c9563608SJanet Adkins         return "m";
192c9563608SJanet Adkins     }
193c9563608SJanet Adkins     if (sensorType == "airflow")
194c9563608SJanet Adkins     {
195c9563608SJanet Adkins         return "cft_i/min";
196c9563608SJanet Adkins     }
197c9563608SJanet Adkins     if (sensorType == "energy")
198c9563608SJanet Adkins     {
199c9563608SJanet Adkins         return "J";
200c9563608SJanet Adkins     }
201c9563608SJanet Adkins     return "";
202c9563608SJanet Adkins }
203c9563608SJanet Adkins 
204c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType)
205c9563608SJanet Adkins {
206c9563608SJanet Adkins     if (sensorType == "voltage")
207c9563608SJanet Adkins     {
208c9563608SJanet Adkins         return sensor::ReadingType::Voltage;
209c9563608SJanet Adkins     }
210c9563608SJanet Adkins     if (sensorType == "power")
211c9563608SJanet Adkins     {
212c9563608SJanet Adkins         return sensor::ReadingType::Power;
213c9563608SJanet Adkins     }
214c9563608SJanet Adkins     if (sensorType == "current")
215c9563608SJanet Adkins     {
216c9563608SJanet Adkins         return sensor::ReadingType::Current;
217c9563608SJanet Adkins     }
218c9563608SJanet Adkins     if (sensorType == "fan_tach")
219c9563608SJanet Adkins     {
220c9563608SJanet Adkins         return sensor::ReadingType::Rotational;
221c9563608SJanet Adkins     }
222c9563608SJanet Adkins     if (sensorType == "temperature")
223c9563608SJanet Adkins     {
224c9563608SJanet Adkins         return sensor::ReadingType::Temperature;
225c9563608SJanet Adkins     }
226c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization")
227c9563608SJanet Adkins     {
228c9563608SJanet Adkins         return sensor::ReadingType::Percent;
229c9563608SJanet Adkins     }
230c9563608SJanet Adkins     if (sensorType == "humidity")
231c9563608SJanet Adkins     {
232c9563608SJanet Adkins         return sensor::ReadingType::Humidity;
233c9563608SJanet Adkins     }
234c9563608SJanet Adkins     if (sensorType == "altitude")
235c9563608SJanet Adkins     {
236c9563608SJanet Adkins         return sensor::ReadingType::Altitude;
237c9563608SJanet Adkins     }
238c9563608SJanet Adkins     if (sensorType == "airflow")
239c9563608SJanet Adkins     {
240c9563608SJanet Adkins         return sensor::ReadingType::AirFlow;
241c9563608SJanet Adkins     }
242c9563608SJanet Adkins     if (sensorType == "energy")
243c9563608SJanet Adkins     {
244c9563608SJanet Adkins         return sensor::ReadingType::EnergyJoules;
245c9563608SJanet Adkins     }
246c9563608SJanet Adkins     return sensor::ReadingType::Invalid;
247c9563608SJanet Adkins }
248c9563608SJanet Adkins 
249c9563608SJanet Adkins } // namespace sensors
250c9563608SJanet Adkins 
251c9563608SJanet Adkins /**
252c9563608SJanet Adkins  * @brief Returns the Redfish State value for the specified inventory item.
253c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with a sensor.
254c9563608SJanet Adkins  * @param sensorAvailable Boolean representing if D-Bus sensor is marked as
255c9563608SJanet Adkins  * available.
256c9563608SJanet Adkins  * @return State value for inventory item.
257c9563608SJanet Adkins  */
258c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem,
259c9563608SJanet Adkins                                 const bool sensorAvailable)
260c9563608SJanet Adkins {
261c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
262c9563608SJanet Adkins     {
263c9563608SJanet Adkins         return resource::State::Absent;
264c9563608SJanet Adkins     }
265c9563608SJanet Adkins 
266c9563608SJanet Adkins     if (!sensorAvailable)
267c9563608SJanet Adkins     {
268c9563608SJanet Adkins         return resource::State::UnavailableOffline;
269c9563608SJanet Adkins     }
270c9563608SJanet Adkins 
271c9563608SJanet Adkins     return resource::State::Enabled;
272c9563608SJanet Adkins }
273c9563608SJanet Adkins 
274c9563608SJanet Adkins /**
275c9563608SJanet Adkins  * @brief Returns the Redfish Health value for the specified sensor.
276c9563608SJanet Adkins  * @param sensorJson Sensor JSON object.
277c9563608SJanet Adkins  * @param valuesDict Map of all sensor DBus values.
278c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
279c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
280c9563608SJanet Adkins  * @return Health value for sensor.
281c9563608SJanet Adkins  */
282c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson,
283c9563608SJanet Adkins                              const dbus::utility::DBusPropertiesMap& valuesDict,
284c9563608SJanet Adkins                              const InventoryItem* inventoryItem)
285c9563608SJanet Adkins {
286c9563608SJanet Adkins     // Get current health value (if any) in the sensor JSON object.  Some JSON
287c9563608SJanet Adkins     // objects contain multiple sensors (such as PowerSupplies).  We want to set
288c9563608SJanet Adkins     // the overall health to be the most severe of any of the sensors.
289c9563608SJanet Adkins     std::string currentHealth;
290c9563608SJanet Adkins     auto statusIt = sensorJson.find("Status");
291c9563608SJanet Adkins     if (statusIt != sensorJson.end())
292c9563608SJanet Adkins     {
293c9563608SJanet Adkins         auto healthIt = statusIt->find("Health");
294c9563608SJanet Adkins         if (healthIt != statusIt->end())
295c9563608SJanet Adkins         {
296c9563608SJanet Adkins             std::string* health = healthIt->get_ptr<std::string*>();
297c9563608SJanet Adkins             if (health != nullptr)
298c9563608SJanet Adkins             {
299c9563608SJanet Adkins                 currentHealth = *health;
300c9563608SJanet Adkins             }
301c9563608SJanet Adkins         }
302c9563608SJanet Adkins     }
303c9563608SJanet Adkins 
304c9563608SJanet Adkins     // If current health in JSON object is already Critical, return that.  This
305c9563608SJanet Adkins     // should override the sensor health, which might be less severe.
306c9563608SJanet Adkins     if (currentHealth == "Critical")
307c9563608SJanet Adkins     {
308c9563608SJanet Adkins         return "Critical";
309c9563608SJanet Adkins     }
310c9563608SJanet Adkins 
311c9563608SJanet Adkins     const bool* criticalAlarmHigh = nullptr;
312c9563608SJanet Adkins     const bool* criticalAlarmLow = nullptr;
313c9563608SJanet Adkins     const bool* warningAlarmHigh = nullptr;
314c9563608SJanet Adkins     const bool* warningAlarmLow = nullptr;
315c9563608SJanet Adkins 
316c9563608SJanet Adkins     const bool success = sdbusplus::unpackPropertiesNoThrow(
317c9563608SJanet Adkins         dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh",
318c9563608SJanet Adkins         criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow,
319c9563608SJanet Adkins         "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow",
320c9563608SJanet Adkins         warningAlarmLow);
321c9563608SJanet Adkins 
322c9563608SJanet Adkins     if (success)
323c9563608SJanet Adkins     {
324c9563608SJanet Adkins         // Check if sensor has critical threshold alarm
325c9563608SJanet Adkins         if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) ||
326c9563608SJanet Adkins             (criticalAlarmLow != nullptr && *criticalAlarmLow))
327c9563608SJanet Adkins         {
328c9563608SJanet Adkins             return "Critical";
329c9563608SJanet Adkins         }
330c9563608SJanet Adkins     }
331c9563608SJanet Adkins 
332c9563608SJanet Adkins     // Check if associated inventory item is not functional
333c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
334c9563608SJanet Adkins     {
335c9563608SJanet Adkins         return "Critical";
336c9563608SJanet Adkins     }
337c9563608SJanet Adkins 
338c9563608SJanet Adkins     // If current health in JSON object is already Warning, return that. This
339c9563608SJanet Adkins     // should override the sensor status, which might be less severe.
340c9563608SJanet Adkins     if (currentHealth == "Warning")
341c9563608SJanet Adkins     {
342c9563608SJanet Adkins         return "Warning";
343c9563608SJanet Adkins     }
344c9563608SJanet Adkins 
345c9563608SJanet Adkins     if (success)
346c9563608SJanet Adkins     {
347c9563608SJanet Adkins         // Check if sensor has warning threshold alarm
348c9563608SJanet Adkins         if ((warningAlarmHigh != nullptr && *warningAlarmHigh) ||
349c9563608SJanet Adkins             (warningAlarmLow != nullptr && *warningAlarmLow))
350c9563608SJanet Adkins         {
351c9563608SJanet Adkins             return "Warning";
352c9563608SJanet Adkins         }
353c9563608SJanet Adkins     }
354c9563608SJanet Adkins 
355c9563608SJanet Adkins     return "OK";
356c9563608SJanet Adkins }
357c9563608SJanet Adkins 
358c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson,
359c9563608SJanet Adkins                         const InventoryItem* inventoryItem)
360c9563608SJanet Adkins {
361c9563608SJanet Adkins     if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty())
362c9563608SJanet Adkins     {
363c9563608SJanet Adkins         switch (inventoryItem->ledState)
364c9563608SJanet Adkins         {
365c9563608SJanet Adkins             case LedState::OFF:
366c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off;
367c9563608SJanet Adkins                 break;
368c9563608SJanet Adkins             case LedState::ON:
369c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit;
370c9563608SJanet Adkins                 break;
371c9563608SJanet Adkins             case LedState::BLINK:
372c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking;
373c9563608SJanet Adkins                 break;
374c9563608SJanet Adkins             default:
375c9563608SJanet Adkins                 break;
376c9563608SJanet Adkins         }
377c9563608SJanet Adkins     }
378c9563608SJanet Adkins }
379c9563608SJanet Adkins 
380c9563608SJanet Adkins /**
381c9563608SJanet Adkins  * @brief Builds a json sensor representation of a sensor.
382c9563608SJanet Adkins  * @param sensorName  The name of the sensor to be built
383c9563608SJanet Adkins  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
384c9563608SJanet Adkins  * build
385c9563608SJanet Adkins  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
386c9563608SJanet Adkins  * @param propertiesDict A dictionary of the properties to build the sensor
387c9563608SJanet Adkins  * from.
388c9563608SJanet Adkins  * @param sensorJson  The json object to fill
389c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
390c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
391c9563608SJanet Adkins  */
392c9563608SJanet Adkins inline void objectPropertiesToJson(
393c9563608SJanet Adkins     std::string_view sensorName, std::string_view sensorType,
3940c728b42SJanet Adkins     ChassisSubNode chassisSubNode,
395c9563608SJanet Adkins     const dbus::utility::DBusPropertiesMap& propertiesDict,
396c9563608SJanet Adkins     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
397c9563608SJanet Adkins {
3986fe8751cSGeorge Liu     // Parameter to set to override the type we get from dbus, and force it to
3996fe8751cSGeorge Liu     // int, regardless of what is available.  This is used for schemas like fan,
4006fe8751cSGeorge Liu     // that require integers, not floats.
4016fe8751cSGeorge Liu     bool forceToInt = false;
4026fe8751cSGeorge Liu 
4036fe8751cSGeorge Liu     nlohmann::json::json_pointer unit("/Reading");
4046fe8751cSGeorge Liu 
4056fe8751cSGeorge Liu     // This ChassisSubNode builds sensor excerpts
4066fe8751cSGeorge Liu     bool isExcerpt = isExcerptNode(chassisSubNode);
4076fe8751cSGeorge Liu 
4086fe8751cSGeorge Liu     /* Sensor excerpts use different keys to reference the sensor. These are
4096fe8751cSGeorge Liu      * built by the caller.
4106fe8751cSGeorge Liu      * Additionally they don't include these additional properties.
4116fe8751cSGeorge Liu      */
4126fe8751cSGeorge Liu     if (!isExcerpt)
4136fe8751cSGeorge Liu     {
4140c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
415c9563608SJanet Adkins         {
416c9563608SJanet Adkins             std::string subNodeEscaped = getSensorId(sensorName, sensorType);
417c9563608SJanet Adkins             // For sensors in SensorCollection we set Id instead of MemberId,
418c9563608SJanet Adkins             // including power sensors.
419c9563608SJanet Adkins             sensorJson["Id"] = std::move(subNodeEscaped);
420c9563608SJanet Adkins 
421c9563608SJanet Adkins             std::string sensorNameEs(sensorName);
422c9563608SJanet Adkins             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
423c9563608SJanet Adkins             sensorJson["Name"] = std::move(sensorNameEs);
424c9563608SJanet Adkins         }
425c9563608SJanet Adkins         else if (sensorType != "power")
426c9563608SJanet Adkins         {
4276fe8751cSGeorge Liu             // Set MemberId and Name for non-power sensors.  For PowerSupplies
4286fe8751cSGeorge Liu             // and PowerControl, those properties have more general values
4296fe8751cSGeorge Liu             // because multiple sensors can be stored in the same JSON object.
430c9563608SJanet Adkins             std::string sensorNameEs(sensorName);
431c9563608SJanet Adkins             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
432c9563608SJanet Adkins             sensorJson["Name"] = std::move(sensorNameEs);
433c9563608SJanet Adkins         }
434c9563608SJanet Adkins 
435c9563608SJanet Adkins         const bool* checkAvailable = nullptr;
436c9563608SJanet Adkins         bool available = true;
437c9563608SJanet Adkins         const bool success = sdbusplus::unpackPropertiesNoThrow(
438c9563608SJanet Adkins             dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available",
439c9563608SJanet Adkins             checkAvailable);
440c9563608SJanet Adkins         if (!success)
441c9563608SJanet Adkins         {
442c9563608SJanet Adkins             messages::internalError();
443c9563608SJanet Adkins         }
444c9563608SJanet Adkins         if (checkAvailable != nullptr)
445c9563608SJanet Adkins         {
446c9563608SJanet Adkins             available = *checkAvailable;
447c9563608SJanet Adkins         }
448c9563608SJanet Adkins 
449c9563608SJanet Adkins         sensorJson["Status"]["State"] = getState(inventoryItem, available);
450c9563608SJanet Adkins         sensorJson["Status"]["Health"] =
451c9563608SJanet Adkins             getHealth(sensorJson, propertiesDict, inventoryItem);
452c9563608SJanet Adkins 
4530c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
454c9563608SJanet Adkins         {
455c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor";
456c9563608SJanet Adkins 
4576fe8751cSGeorge Liu             sensor::ReadingType readingType =
4586fe8751cSGeorge Liu                 sensors::toReadingType(sensorType);
459c9563608SJanet Adkins             if (readingType == sensor::ReadingType::Invalid)
460c9563608SJanet Adkins             {
461c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}",
462c9563608SJanet Adkins                                  sensorType);
463c9563608SJanet Adkins             }
464c9563608SJanet Adkins             else
465c9563608SJanet Adkins             {
466c9563608SJanet Adkins                 sensorJson["ReadingType"] = readingType;
467c9563608SJanet Adkins             }
468c9563608SJanet Adkins 
469c9563608SJanet Adkins             std::string_view readingUnits = sensors::toReadingUnits(sensorType);
470c9563608SJanet Adkins             if (readingUnits.empty())
471c9563608SJanet Adkins             {
472c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}",
473c9563608SJanet Adkins                                  sensorType);
474c9563608SJanet Adkins             }
475c9563608SJanet Adkins             else
476c9563608SJanet Adkins             {
477c9563608SJanet Adkins                 sensorJson["ReadingUnits"] = readingUnits;
478c9563608SJanet Adkins             }
479c9563608SJanet Adkins         }
480c9563608SJanet Adkins         else if (sensorType == "temperature")
481c9563608SJanet Adkins         {
482c9563608SJanet Adkins             unit = "/ReadingCelsius"_json_pointer;
483c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature";
484c9563608SJanet Adkins             // TODO(ed) Documentation says that path should be type fan_tach,
485c9563608SJanet Adkins             // implementation seems to implement fan
486c9563608SJanet Adkins         }
487c9563608SJanet Adkins         else if (sensorType == "fan" || sensorType == "fan_tach")
488c9563608SJanet Adkins         {
489c9563608SJanet Adkins             unit = "/Reading"_json_pointer;
490c9563608SJanet Adkins             sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM;
491c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
492c9563608SJanet Adkins             setLedState(sensorJson, inventoryItem);
493c9563608SJanet Adkins             forceToInt = true;
494c9563608SJanet Adkins         }
495c9563608SJanet Adkins         else if (sensorType == "fan_pwm")
496c9563608SJanet Adkins         {
497c9563608SJanet Adkins             unit = "/Reading"_json_pointer;
498c9563608SJanet Adkins             sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent;
499c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
500c9563608SJanet Adkins             setLedState(sensorJson, inventoryItem);
501c9563608SJanet Adkins             forceToInt = true;
502c9563608SJanet Adkins         }
503c9563608SJanet Adkins         else if (sensorType == "voltage")
504c9563608SJanet Adkins         {
505c9563608SJanet Adkins             unit = "/ReadingVolts"_json_pointer;
506c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
507c9563608SJanet Adkins         }
508c9563608SJanet Adkins         else if (sensorType == "power")
509c9563608SJanet Adkins         {
510c9563608SJanet Adkins             std::string lower;
511c9563608SJanet Adkins             std::ranges::transform(sensorName, std::back_inserter(lower),
512c9563608SJanet Adkins                                    bmcweb::asciiToLower);
513c9563608SJanet Adkins             if (lower == "total_power")
514c9563608SJanet Adkins             {
515c9563608SJanet Adkins                 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
516c9563608SJanet Adkins                 // Put multiple "sensors" into a single PowerControl, so have
517c9563608SJanet Adkins                 // generic names for MemberId and Name. Follows Redfish mockup.
518c9563608SJanet Adkins                 sensorJson["MemberId"] = "0";
519c9563608SJanet Adkins                 sensorJson["Name"] = "Chassis Power Control";
520c9563608SJanet Adkins                 unit = "/PowerConsumedWatts"_json_pointer;
521c9563608SJanet Adkins             }
522c9563608SJanet Adkins             else if (lower.find("input") != std::string::npos)
523c9563608SJanet Adkins             {
524c9563608SJanet Adkins                 unit = "/PowerInputWatts"_json_pointer;
525c9563608SJanet Adkins             }
526c9563608SJanet Adkins             else
527c9563608SJanet Adkins             {
528c9563608SJanet Adkins                 unit = "/PowerOutputWatts"_json_pointer;
529c9563608SJanet Adkins             }
530c9563608SJanet Adkins         }
531c9563608SJanet Adkins         else
532c9563608SJanet Adkins         {
5336fe8751cSGeorge Liu             BMCWEB_LOG_ERROR("Redfish cannot map object type for {}",
5346fe8751cSGeorge Liu                              sensorName);
535c9563608SJanet Adkins             return;
536c9563608SJanet Adkins         }
5376fe8751cSGeorge Liu     }
5386fe8751cSGeorge Liu 
539c9563608SJanet Adkins     // Map of dbus interface name, dbus property name and redfish property_name
540c9563608SJanet Adkins     std::vector<
541c9563608SJanet Adkins         std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
542c9563608SJanet Adkins         properties;
543c9563608SJanet Adkins 
544c9563608SJanet Adkins     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
545c9563608SJanet Adkins 
5466fe8751cSGeorge Liu     if (!isExcerpt)
5476fe8751cSGeorge Liu     {
5480c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
549c9563608SJanet Adkins         {
550c9563608SJanet Adkins             properties.emplace_back(
551c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
552c9563608SJanet Adkins                 "/Thresholds/UpperCaution/Reading"_json_pointer);
553c9563608SJanet Adkins             properties.emplace_back(
554c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
555c9563608SJanet Adkins                 "/Thresholds/LowerCaution/Reading"_json_pointer);
556c9563608SJanet Adkins             properties.emplace_back(
557c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
558c9563608SJanet Adkins                 "/Thresholds/UpperCritical/Reading"_json_pointer);
559c9563608SJanet Adkins             properties.emplace_back(
560c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
561c9563608SJanet Adkins                 "/Thresholds/LowerCritical/Reading"_json_pointer);
562cd5a898fSJanet Adkins 
563cd5a898fSJanet Adkins             /* Add additional properties specific to sensorType */
564cd5a898fSJanet Adkins             if (sensorType == "fan_tach")
565cd5a898fSJanet Adkins             {
5666fe8751cSGeorge Liu                 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
5676fe8751cSGeorge Liu                                         "Value", "/SpeedRPM"_json_pointer);
568cd5a898fSJanet Adkins             }
569c9563608SJanet Adkins         }
570c9563608SJanet Adkins         else if (sensorType != "power")
571c9563608SJanet Adkins         {
5726fe8751cSGeorge Liu             properties.emplace_back(
5736fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
574c9563608SJanet Adkins                 "/UpperThresholdNonCritical"_json_pointer);
5756fe8751cSGeorge Liu             properties.emplace_back(
5766fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
577c9563608SJanet Adkins                 "/LowerThresholdNonCritical"_json_pointer);
5786fe8751cSGeorge Liu             properties.emplace_back(
5796fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
580c9563608SJanet Adkins                 "/UpperThresholdCritical"_json_pointer);
5816fe8751cSGeorge Liu             properties.emplace_back(
5826fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
583c9563608SJanet Adkins                 "/LowerThresholdCritical"_json_pointer);
584c9563608SJanet Adkins         }
585c9563608SJanet Adkins 
586c9563608SJanet Adkins         // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
587c9563608SJanet Adkins 
5880c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
589c9563608SJanet Adkins         {
5906fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
5916fe8751cSGeorge Liu                                     "MinValue",
592c9563608SJanet Adkins                                     "/ReadingRangeMin"_json_pointer);
5936fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
5946fe8751cSGeorge Liu                                     "MaxValue",
595c9563608SJanet Adkins                                     "/ReadingRangeMax"_json_pointer);
596c9563608SJanet Adkins             properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
597c9563608SJanet Adkins                                     "Accuracy", "/Accuracy"_json_pointer);
598c9563608SJanet Adkins         }
599c9563608SJanet Adkins         else if (sensorType == "temperature")
600c9563608SJanet Adkins         {
6016fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6026fe8751cSGeorge Liu                                     "MinValue",
603c9563608SJanet Adkins                                     "/MinReadingRangeTemp"_json_pointer);
6046fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6056fe8751cSGeorge Liu                                     "MaxValue",
606c9563608SJanet Adkins                                     "/MaxReadingRangeTemp"_json_pointer);
607c9563608SJanet Adkins         }
608c9563608SJanet Adkins         else if (sensorType != "power")
609c9563608SJanet Adkins         {
6106fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6116fe8751cSGeorge Liu                                     "MinValue",
612c9563608SJanet Adkins                                     "/MinReadingRange"_json_pointer);
6136fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6146fe8751cSGeorge Liu                                     "MaxValue",
615c9563608SJanet Adkins                                     "/MaxReadingRange"_json_pointer);
616c9563608SJanet Adkins         }
6176fe8751cSGeorge Liu     }
618c9563608SJanet Adkins 
619c9563608SJanet Adkins     for (const std::tuple<const char*, const char*,
620c9563608SJanet Adkins                           nlohmann::json::json_pointer>& p : properties)
621c9563608SJanet Adkins     {
622c9563608SJanet Adkins         for (const auto& [valueName, valueVariant] : propertiesDict)
623c9563608SJanet Adkins         {
624c9563608SJanet Adkins             if (valueName != std::get<1>(p))
625c9563608SJanet Adkins             {
626c9563608SJanet Adkins                 continue;
627c9563608SJanet Adkins             }
628c9563608SJanet Adkins 
629c9563608SJanet Adkins             // The property we want to set may be nested json, so use
630c9563608SJanet Adkins             // a json_pointer for easy indexing into the json structure.
631c9563608SJanet Adkins             const nlohmann::json::json_pointer& key = std::get<2>(p);
632c9563608SJanet Adkins 
633c9563608SJanet Adkins             const double* doubleValue = std::get_if<double>(&valueVariant);
634c9563608SJanet Adkins             if (doubleValue == nullptr)
635c9563608SJanet Adkins             {
636c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
637c9563608SJanet Adkins                 continue;
638c9563608SJanet Adkins             }
639c9563608SJanet Adkins             if (!std::isfinite(*doubleValue))
640c9563608SJanet Adkins             {
641c9563608SJanet Adkins                 if (valueName == "Value")
642c9563608SJanet Adkins                 {
643c9563608SJanet Adkins                     // Readings are allowed to be NAN for unavailable;  coerce
644c9563608SJanet Adkins                     // them to null in the json response.
645c9563608SJanet Adkins                     sensorJson[key] = nullptr;
646c9563608SJanet Adkins                     continue;
647c9563608SJanet Adkins                 }
648c9563608SJanet Adkins                 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
649c9563608SJanet Adkins                                    valueName, *doubleValue);
650c9563608SJanet Adkins                 continue;
651c9563608SJanet Adkins             }
652c9563608SJanet Adkins             if (forceToInt)
653c9563608SJanet Adkins             {
654c9563608SJanet Adkins                 sensorJson[key] = static_cast<int64_t>(*doubleValue);
655c9563608SJanet Adkins             }
656c9563608SJanet Adkins             else
657c9563608SJanet Adkins             {
658c9563608SJanet Adkins                 sensorJson[key] = *doubleValue;
659c9563608SJanet Adkins             }
660c9563608SJanet Adkins         }
661c9563608SJanet Adkins     }
662c9563608SJanet Adkins }
663c9563608SJanet Adkins 
6646fe8751cSGeorge Liu /**
6656fe8751cSGeorge Liu  * @brief Builds a json sensor excerpt representation of a sensor.
6666fe8751cSGeorge Liu  *
6676fe8751cSGeorge Liu  * @details This is a wrapper function to provide consistent setting of
6686fe8751cSGeorge Liu  * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor
6696fe8751cSGeorge Liu  * excerpts usually have just the D-Bus path for the sensor that is accepted
6706fe8751cSGeorge Liu  * and used to build "DataSourceUri".
6716fe8751cSGeorge Liu 
6726fe8751cSGeorge Liu  * @param path The D-Bus path to the sensor to be built
6736fe8751cSGeorge Liu  * @param chassisId The Chassis Id for the sensor
6746fe8751cSGeorge Liu  * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor
6756fe8751cSGeorge Liu  * @param sensorTypeExpected The expected type of the sensor
6766fe8751cSGeorge Liu  * @param propertiesDict A dictionary of the properties to build the sensor
6776fe8751cSGeorge Liu  * from.
6786fe8751cSGeorge Liu  * @param sensorJson  The json object to fill
6796fe8751cSGeorge Liu  * @returns True if sensorJson object filled. False on any error.
6806fe8751cSGeorge Liu  * Caller is responsible for handling error.
6816fe8751cSGeorge Liu  */
6826fe8751cSGeorge Liu inline bool objectExcerptToJson(
6836fe8751cSGeorge Liu     const std::string& path, const std::string_view chassisId,
6846fe8751cSGeorge Liu     ChassisSubNode chassisSubNode,
6856fe8751cSGeorge Liu     const std::optional<std::string>& sensorTypeExpected,
6866fe8751cSGeorge Liu     const dbus::utility::DBusPropertiesMap& propertiesDict,
6876fe8751cSGeorge Liu     nlohmann::json& sensorJson)
6886fe8751cSGeorge Liu {
6896fe8751cSGeorge Liu     if (!isExcerptNode(chassisSubNode))
6906fe8751cSGeorge Liu     {
6916fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("{} is not a sensor excerpt",
6926fe8751cSGeorge Liu                          chassisSubNodeToString(chassisSubNode));
6936fe8751cSGeorge Liu         return false;
6946fe8751cSGeorge Liu     }
6956fe8751cSGeorge Liu 
6966fe8751cSGeorge Liu     sdbusplus::message::object_path sensorPath(path);
6976fe8751cSGeorge Liu     std::string sensorName = sensorPath.filename();
6986fe8751cSGeorge Liu     std::string sensorType = sensorPath.parent_path().filename();
6996fe8751cSGeorge Liu     if (sensorName.empty() || sensorType.empty())
7006fe8751cSGeorge Liu     {
7016fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("Invalid sensor path {}", path);
7026fe8751cSGeorge Liu         return false;
7036fe8751cSGeorge Liu     }
7046fe8751cSGeorge Liu 
7056fe8751cSGeorge Liu     if (sensorTypeExpected && (sensorType != *sensorTypeExpected))
7066fe8751cSGeorge Liu     {
7076fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("{} is not expected type {}", path,
7086fe8751cSGeorge Liu                          *sensorTypeExpected);
7096fe8751cSGeorge Liu         return false;
7106fe8751cSGeorge Liu     }
7116fe8751cSGeorge Liu 
7126fe8751cSGeorge Liu     // Sensor excerpts use DataSourceUri to reference full sensor Redfish path
7136fe8751cSGeorge Liu     sensorJson["DataSourceUri"] =
7146fe8751cSGeorge Liu         boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId,
7156fe8751cSGeorge Liu                             getSensorId(sensorName, sensorType));
7166fe8751cSGeorge Liu 
7176fe8751cSGeorge Liu     // Fill in sensor excerpt properties
7186fe8751cSGeorge Liu     objectPropertiesToJson(sensorName, sensorType, chassisSubNode,
7196fe8751cSGeorge Liu                            propertiesDict, sensorJson, nullptr);
7206fe8751cSGeorge Liu 
7216fe8751cSGeorge Liu     return true;
7226fe8751cSGeorge Liu }
7236fe8751cSGeorge Liu 
7246fe8751cSGeorge Liu // Maps D-Bus: Service, SensorPath
7256fe8751cSGeorge Liu using SensorServicePathMap = std::pair<std::string, std::string>;
7266fe8751cSGeorge Liu using SensorServicePathList = std::vector<SensorServicePathMap>;
7276fe8751cSGeorge Liu 
7286fe8751cSGeorge Liu inline void getAllSensorObjects(
7296fe8751cSGeorge Liu     const std::string& associatedPath, const std::string& path,
7306fe8751cSGeorge Liu     std::span<const std::string_view> interfaces, const int32_t depth,
7316fe8751cSGeorge Liu     std::function<void(const boost::system::error_code& ec,
7326fe8751cSGeorge Liu                        SensorServicePathList&)>&& callback)
7336fe8751cSGeorge Liu {
7346fe8751cSGeorge Liu     sdbusplus::message::object_path endpointPath{associatedPath};
7356fe8751cSGeorge Liu     endpointPath /= "all_sensors";
7366fe8751cSGeorge Liu 
7376fe8751cSGeorge Liu     dbus::utility::getAssociatedSubTree(
7386fe8751cSGeorge Liu         endpointPath, sdbusplus::message::object_path(path), depth, interfaces,
7396fe8751cSGeorge Liu         [callback = std::move(callback)](
7406fe8751cSGeorge Liu             const boost::system::error_code& ec,
7416fe8751cSGeorge Liu             const dbus::utility::MapperGetSubTreeResponse& subtree) {
7426fe8751cSGeorge Liu             SensorServicePathList sensorsServiceAndPath;
7436fe8751cSGeorge Liu 
7446fe8751cSGeorge Liu             if (ec)
7456fe8751cSGeorge Liu             {
7466fe8751cSGeorge Liu                 callback(ec, sensorsServiceAndPath);
7476fe8751cSGeorge Liu                 return;
7486fe8751cSGeorge Liu             }
7496fe8751cSGeorge Liu 
7506fe8751cSGeorge Liu             for (const auto& [sensorPath, serviceMaps] : subtree)
7516fe8751cSGeorge Liu             {
7526fe8751cSGeorge Liu                 for (const auto& [service, mapInterfaces] : serviceMaps)
7536fe8751cSGeorge Liu                 {
7546fe8751cSGeorge Liu                     sensorsServiceAndPath.emplace_back(service, sensorPath);
7556fe8751cSGeorge Liu                 }
7566fe8751cSGeorge Liu             }
7576fe8751cSGeorge Liu 
7586fe8751cSGeorge Liu             callback(ec, sensorsServiceAndPath);
7596fe8751cSGeorge Liu         });
7606fe8751cSGeorge Liu }
7616fe8751cSGeorge Liu 
7621516c21bSJanet Adkins } // namespace sensor_utils
7631516c21bSJanet Adkins } // namespace redfish
764