xref: /openbmc/bmcweb/features/redfish/include/utils/sensor_utils.hpp (revision 6fe8751c46d7086e49977b68e74262927b4a7d39)
11516c21bSJanet Adkins #pragma once
21516c21bSJanet Adkins 
3c9563608SJanet Adkins #include "dbus_utility.hpp"
4c9563608SJanet Adkins #include "generated/enums/resource.hpp"
5c9563608SJanet Adkins #include "generated/enums/sensor.hpp"
6c9563608SJanet Adkins #include "generated/enums/thermal.hpp"
7c9563608SJanet Adkins #include "str_utility.hpp"
8c9563608SJanet Adkins #include "utils/dbus_utils.hpp"
9c9563608SJanet Adkins #include "utils/json_utils.hpp"
10c9563608SJanet Adkins 
11*6fe8751cSGeorge Liu #include <boost/url/format.hpp>
12c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp>
13c9563608SJanet Adkins 
141516c21bSJanet Adkins #include <algorithm>
151516c21bSJanet Adkins #include <format>
16*6fe8751cSGeorge Liu #include <functional>
17*6fe8751cSGeorge Liu #include <optional>
181516c21bSJanet Adkins #include <ranges>
191516c21bSJanet Adkins #include <string>
201516c21bSJanet Adkins #include <string_view>
21c9563608SJanet Adkins #include <tuple>
221516c21bSJanet Adkins #include <utility>
231516c21bSJanet Adkins #include <vector>
241516c21bSJanet Adkins 
251516c21bSJanet Adkins namespace redfish
261516c21bSJanet Adkins {
271516c21bSJanet Adkins namespace sensor_utils
281516c21bSJanet Adkins {
291516c21bSJanet Adkins 
300c728b42SJanet Adkins enum class ChassisSubNode
310c728b42SJanet Adkins {
320c728b42SJanet Adkins     powerNode,
330c728b42SJanet Adkins     sensorsNode,
340c728b42SJanet Adkins     thermalNode,
35*6fe8751cSGeorge Liu     thermalMetricsNode,
360c728b42SJanet Adkins     unknownNode,
370c728b42SJanet Adkins };
380c728b42SJanet Adkins 
390c728b42SJanet Adkins constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode)
400c728b42SJanet Adkins {
410c728b42SJanet Adkins     switch (subNode)
420c728b42SJanet Adkins     {
430c728b42SJanet Adkins         case ChassisSubNode::powerNode:
440c728b42SJanet Adkins             return "Power";
450c728b42SJanet Adkins         case ChassisSubNode::sensorsNode:
460c728b42SJanet Adkins             return "Sensors";
470c728b42SJanet Adkins         case ChassisSubNode::thermalNode:
480c728b42SJanet Adkins             return "Thermal";
49*6fe8751cSGeorge Liu         case ChassisSubNode::thermalMetricsNode:
50*6fe8751cSGeorge Liu             return "ThermalMetrics";
510c728b42SJanet Adkins         case ChassisSubNode::unknownNode:
520c728b42SJanet Adkins         default:
530c728b42SJanet Adkins             return "";
540c728b42SJanet Adkins     }
550c728b42SJanet Adkins }
560c728b42SJanet Adkins 
570c728b42SJanet Adkins inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr)
580c728b42SJanet Adkins {
590c728b42SJanet Adkins     // If none match unknownNode is returned
600c728b42SJanet Adkins     ChassisSubNode subNode = ChassisSubNode::unknownNode;
610c728b42SJanet Adkins 
620c728b42SJanet Adkins     if (subNodeStr == "Power")
630c728b42SJanet Adkins     {
640c728b42SJanet Adkins         subNode = ChassisSubNode::powerNode;
650c728b42SJanet Adkins     }
660c728b42SJanet Adkins     else if (subNodeStr == "Sensors")
670c728b42SJanet Adkins     {
680c728b42SJanet Adkins         subNode = ChassisSubNode::sensorsNode;
690c728b42SJanet Adkins     }
700c728b42SJanet Adkins     else if (subNodeStr == "Thermal")
710c728b42SJanet Adkins     {
720c728b42SJanet Adkins         subNode = ChassisSubNode::thermalNode;
730c728b42SJanet Adkins     }
74*6fe8751cSGeorge Liu     else if (subNodeStr == "ThermalMetrics")
75*6fe8751cSGeorge Liu     {
76*6fe8751cSGeorge Liu         subNode = ChassisSubNode::thermalMetricsNode;
77*6fe8751cSGeorge Liu     }
780c728b42SJanet Adkins 
790c728b42SJanet Adkins     return subNode;
800c728b42SJanet Adkins }
81c9563608SJanet Adkins 
82*6fe8751cSGeorge Liu inline bool isExcerptNode(const ChassisSubNode subNode)
83*6fe8751cSGeorge Liu {
84*6fe8751cSGeorge Liu     return (subNode == ChassisSubNode::thermalMetricsNode);
85*6fe8751cSGeorge Liu }
86*6fe8751cSGeorge Liu 
87c9563608SJanet Adkins /**
88c9563608SJanet Adkins  * Possible states for physical inventory leds
89c9563608SJanet Adkins  */
90c9563608SJanet Adkins enum class LedState
91c9563608SJanet Adkins {
92c9563608SJanet Adkins     OFF,
93c9563608SJanet Adkins     ON,
94c9563608SJanet Adkins     BLINK,
95c9563608SJanet Adkins     UNKNOWN
96c9563608SJanet Adkins };
97c9563608SJanet Adkins 
98c9563608SJanet Adkins /**
99c9563608SJanet Adkins  * D-Bus inventory item associated with one or more sensors.
100c9563608SJanet Adkins  */
101c9563608SJanet Adkins class InventoryItem
102c9563608SJanet Adkins {
103c9563608SJanet Adkins   public:
104c9563608SJanet Adkins     explicit InventoryItem(const std::string& objPath) : objectPath(objPath)
105c9563608SJanet Adkins     {
106c9563608SJanet Adkins         // Set inventory item name to last node of object path
107c9563608SJanet Adkins         sdbusplus::message::object_path path(objectPath);
108c9563608SJanet Adkins         name = path.filename();
109c9563608SJanet Adkins         if (name.empty())
110c9563608SJanet Adkins         {
111c9563608SJanet Adkins             BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath);
112c9563608SJanet Adkins         }
113c9563608SJanet Adkins     }
114c9563608SJanet Adkins 
115c9563608SJanet Adkins     std::string objectPath;
116c9563608SJanet Adkins     std::string name;
117c9563608SJanet Adkins     bool isPresent = true;
118c9563608SJanet Adkins     bool isFunctional = true;
119c9563608SJanet Adkins     bool isPowerSupply = false;
120c9563608SJanet Adkins     int powerSupplyEfficiencyPercent = -1;
121c9563608SJanet Adkins     std::string manufacturer;
122c9563608SJanet Adkins     std::string model;
123c9563608SJanet Adkins     std::string partNumber;
124c9563608SJanet Adkins     std::string serialNumber;
125c9563608SJanet Adkins     std::set<std::string> sensors;
126c9563608SJanet Adkins     std::string ledObjectPath;
127c9563608SJanet Adkins     LedState ledState = LedState::UNKNOWN;
128c9563608SJanet Adkins };
129c9563608SJanet Adkins 
1301516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName,
1311516c21bSJanet Adkins                                std::string_view sensorType)
1321516c21bSJanet Adkins {
1331516c21bSJanet Adkins     std::string normalizedType(sensorType);
1341516c21bSJanet Adkins     auto remove = std::ranges::remove(normalizedType, '_');
1351516c21bSJanet Adkins     normalizedType.erase(std::ranges::begin(remove), normalizedType.end());
1361516c21bSJanet Adkins 
1371516c21bSJanet Adkins     return std::format("{}_{}", normalizedType, sensorName);
1381516c21bSJanet Adkins }
1391516c21bSJanet Adkins 
1401516c21bSJanet Adkins inline std::pair<std::string, std::string>
1411516c21bSJanet Adkins     splitSensorNameAndType(std::string_view sensorId)
1421516c21bSJanet Adkins {
1431516c21bSJanet Adkins     size_t index = sensorId.find('_');
1441516c21bSJanet Adkins     if (index == std::string::npos)
1451516c21bSJanet Adkins     {
1461516c21bSJanet Adkins         return std::make_pair<std::string, std::string>("", "");
1471516c21bSJanet Adkins     }
1481516c21bSJanet Adkins     std::string sensorType{sensorId.substr(0, index)};
1491516c21bSJanet Adkins     std::string sensorName{sensorId.substr(index + 1)};
1501516c21bSJanet Adkins     // fan_pwm and fan_tach need special handling
1511516c21bSJanet Adkins     if (sensorType == "fantach" || sensorType == "fanpwm")
1521516c21bSJanet Adkins     {
1531516c21bSJanet Adkins         sensorType.insert(3, 1, '_');
1541516c21bSJanet Adkins     }
1551516c21bSJanet Adkins     return std::make_pair(sensorType, sensorName);
1561516c21bSJanet Adkins }
1571516c21bSJanet Adkins 
158c9563608SJanet Adkins namespace sensors
159c9563608SJanet Adkins {
160c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType)
161c9563608SJanet Adkins {
162c9563608SJanet Adkins     if (sensorType == "voltage")
163c9563608SJanet Adkins     {
164c9563608SJanet Adkins         return "V";
165c9563608SJanet Adkins     }
166c9563608SJanet Adkins     if (sensorType == "power")
167c9563608SJanet Adkins     {
168c9563608SJanet Adkins         return "W";
169c9563608SJanet Adkins     }
170c9563608SJanet Adkins     if (sensorType == "current")
171c9563608SJanet Adkins     {
172c9563608SJanet Adkins         return "A";
173c9563608SJanet Adkins     }
174c9563608SJanet Adkins     if (sensorType == "fan_tach")
175c9563608SJanet Adkins     {
176c9563608SJanet Adkins         return "RPM";
177c9563608SJanet Adkins     }
178c9563608SJanet Adkins     if (sensorType == "temperature")
179c9563608SJanet Adkins     {
180c9563608SJanet Adkins         return "Cel";
181c9563608SJanet Adkins     }
182c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization" ||
183c9563608SJanet Adkins         sensorType == "humidity")
184c9563608SJanet Adkins     {
185c9563608SJanet Adkins         return "%";
186c9563608SJanet Adkins     }
187c9563608SJanet Adkins     if (sensorType == "altitude")
188c9563608SJanet Adkins     {
189c9563608SJanet Adkins         return "m";
190c9563608SJanet Adkins     }
191c9563608SJanet Adkins     if (sensorType == "airflow")
192c9563608SJanet Adkins     {
193c9563608SJanet Adkins         return "cft_i/min";
194c9563608SJanet Adkins     }
195c9563608SJanet Adkins     if (sensorType == "energy")
196c9563608SJanet Adkins     {
197c9563608SJanet Adkins         return "J";
198c9563608SJanet Adkins     }
199c9563608SJanet Adkins     return "";
200c9563608SJanet Adkins }
201c9563608SJanet Adkins 
202c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType)
203c9563608SJanet Adkins {
204c9563608SJanet Adkins     if (sensorType == "voltage")
205c9563608SJanet Adkins     {
206c9563608SJanet Adkins         return sensor::ReadingType::Voltage;
207c9563608SJanet Adkins     }
208c9563608SJanet Adkins     if (sensorType == "power")
209c9563608SJanet Adkins     {
210c9563608SJanet Adkins         return sensor::ReadingType::Power;
211c9563608SJanet Adkins     }
212c9563608SJanet Adkins     if (sensorType == "current")
213c9563608SJanet Adkins     {
214c9563608SJanet Adkins         return sensor::ReadingType::Current;
215c9563608SJanet Adkins     }
216c9563608SJanet Adkins     if (sensorType == "fan_tach")
217c9563608SJanet Adkins     {
218c9563608SJanet Adkins         return sensor::ReadingType::Rotational;
219c9563608SJanet Adkins     }
220c9563608SJanet Adkins     if (sensorType == "temperature")
221c9563608SJanet Adkins     {
222c9563608SJanet Adkins         return sensor::ReadingType::Temperature;
223c9563608SJanet Adkins     }
224c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization")
225c9563608SJanet Adkins     {
226c9563608SJanet Adkins         return sensor::ReadingType::Percent;
227c9563608SJanet Adkins     }
228c9563608SJanet Adkins     if (sensorType == "humidity")
229c9563608SJanet Adkins     {
230c9563608SJanet Adkins         return sensor::ReadingType::Humidity;
231c9563608SJanet Adkins     }
232c9563608SJanet Adkins     if (sensorType == "altitude")
233c9563608SJanet Adkins     {
234c9563608SJanet Adkins         return sensor::ReadingType::Altitude;
235c9563608SJanet Adkins     }
236c9563608SJanet Adkins     if (sensorType == "airflow")
237c9563608SJanet Adkins     {
238c9563608SJanet Adkins         return sensor::ReadingType::AirFlow;
239c9563608SJanet Adkins     }
240c9563608SJanet Adkins     if (sensorType == "energy")
241c9563608SJanet Adkins     {
242c9563608SJanet Adkins         return sensor::ReadingType::EnergyJoules;
243c9563608SJanet Adkins     }
244c9563608SJanet Adkins     return sensor::ReadingType::Invalid;
245c9563608SJanet Adkins }
246c9563608SJanet Adkins 
247c9563608SJanet Adkins } // namespace sensors
248c9563608SJanet Adkins 
249c9563608SJanet Adkins /**
250c9563608SJanet Adkins  * @brief Returns the Redfish State value for the specified inventory item.
251c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with a sensor.
252c9563608SJanet Adkins  * @param sensorAvailable Boolean representing if D-Bus sensor is marked as
253c9563608SJanet Adkins  * available.
254c9563608SJanet Adkins  * @return State value for inventory item.
255c9563608SJanet Adkins  */
256c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem,
257c9563608SJanet Adkins                                 const bool sensorAvailable)
258c9563608SJanet Adkins {
259c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
260c9563608SJanet Adkins     {
261c9563608SJanet Adkins         return resource::State::Absent;
262c9563608SJanet Adkins     }
263c9563608SJanet Adkins 
264c9563608SJanet Adkins     if (!sensorAvailable)
265c9563608SJanet Adkins     {
266c9563608SJanet Adkins         return resource::State::UnavailableOffline;
267c9563608SJanet Adkins     }
268c9563608SJanet Adkins 
269c9563608SJanet Adkins     return resource::State::Enabled;
270c9563608SJanet Adkins }
271c9563608SJanet Adkins 
272c9563608SJanet Adkins /**
273c9563608SJanet Adkins  * @brief Returns the Redfish Health value for the specified sensor.
274c9563608SJanet Adkins  * @param sensorJson Sensor JSON object.
275c9563608SJanet Adkins  * @param valuesDict Map of all sensor DBus values.
276c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
277c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
278c9563608SJanet Adkins  * @return Health value for sensor.
279c9563608SJanet Adkins  */
280c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson,
281c9563608SJanet Adkins                              const dbus::utility::DBusPropertiesMap& valuesDict,
282c9563608SJanet Adkins                              const InventoryItem* inventoryItem)
283c9563608SJanet Adkins {
284c9563608SJanet Adkins     // Get current health value (if any) in the sensor JSON object.  Some JSON
285c9563608SJanet Adkins     // objects contain multiple sensors (such as PowerSupplies).  We want to set
286c9563608SJanet Adkins     // the overall health to be the most severe of any of the sensors.
287c9563608SJanet Adkins     std::string currentHealth;
288c9563608SJanet Adkins     auto statusIt = sensorJson.find("Status");
289c9563608SJanet Adkins     if (statusIt != sensorJson.end())
290c9563608SJanet Adkins     {
291c9563608SJanet Adkins         auto healthIt = statusIt->find("Health");
292c9563608SJanet Adkins         if (healthIt != statusIt->end())
293c9563608SJanet Adkins         {
294c9563608SJanet Adkins             std::string* health = healthIt->get_ptr<std::string*>();
295c9563608SJanet Adkins             if (health != nullptr)
296c9563608SJanet Adkins             {
297c9563608SJanet Adkins                 currentHealth = *health;
298c9563608SJanet Adkins             }
299c9563608SJanet Adkins         }
300c9563608SJanet Adkins     }
301c9563608SJanet Adkins 
302c9563608SJanet Adkins     // If current health in JSON object is already Critical, return that.  This
303c9563608SJanet Adkins     // should override the sensor health, which might be less severe.
304c9563608SJanet Adkins     if (currentHealth == "Critical")
305c9563608SJanet Adkins     {
306c9563608SJanet Adkins         return "Critical";
307c9563608SJanet Adkins     }
308c9563608SJanet Adkins 
309c9563608SJanet Adkins     const bool* criticalAlarmHigh = nullptr;
310c9563608SJanet Adkins     const bool* criticalAlarmLow = nullptr;
311c9563608SJanet Adkins     const bool* warningAlarmHigh = nullptr;
312c9563608SJanet Adkins     const bool* warningAlarmLow = nullptr;
313c9563608SJanet Adkins 
314c9563608SJanet Adkins     const bool success = sdbusplus::unpackPropertiesNoThrow(
315c9563608SJanet Adkins         dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh",
316c9563608SJanet Adkins         criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow,
317c9563608SJanet Adkins         "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow",
318c9563608SJanet Adkins         warningAlarmLow);
319c9563608SJanet Adkins 
320c9563608SJanet Adkins     if (success)
321c9563608SJanet Adkins     {
322c9563608SJanet Adkins         // Check if sensor has critical threshold alarm
323c9563608SJanet Adkins         if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) ||
324c9563608SJanet Adkins             (criticalAlarmLow != nullptr && *criticalAlarmLow))
325c9563608SJanet Adkins         {
326c9563608SJanet Adkins             return "Critical";
327c9563608SJanet Adkins         }
328c9563608SJanet Adkins     }
329c9563608SJanet Adkins 
330c9563608SJanet Adkins     // Check if associated inventory item is not functional
331c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
332c9563608SJanet Adkins     {
333c9563608SJanet Adkins         return "Critical";
334c9563608SJanet Adkins     }
335c9563608SJanet Adkins 
336c9563608SJanet Adkins     // If current health in JSON object is already Warning, return that. This
337c9563608SJanet Adkins     // should override the sensor status, which might be less severe.
338c9563608SJanet Adkins     if (currentHealth == "Warning")
339c9563608SJanet Adkins     {
340c9563608SJanet Adkins         return "Warning";
341c9563608SJanet Adkins     }
342c9563608SJanet Adkins 
343c9563608SJanet Adkins     if (success)
344c9563608SJanet Adkins     {
345c9563608SJanet Adkins         // Check if sensor has warning threshold alarm
346c9563608SJanet Adkins         if ((warningAlarmHigh != nullptr && *warningAlarmHigh) ||
347c9563608SJanet Adkins             (warningAlarmLow != nullptr && *warningAlarmLow))
348c9563608SJanet Adkins         {
349c9563608SJanet Adkins             return "Warning";
350c9563608SJanet Adkins         }
351c9563608SJanet Adkins     }
352c9563608SJanet Adkins 
353c9563608SJanet Adkins     return "OK";
354c9563608SJanet Adkins }
355c9563608SJanet Adkins 
356c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson,
357c9563608SJanet Adkins                         const InventoryItem* inventoryItem)
358c9563608SJanet Adkins {
359c9563608SJanet Adkins     if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty())
360c9563608SJanet Adkins     {
361c9563608SJanet Adkins         switch (inventoryItem->ledState)
362c9563608SJanet Adkins         {
363c9563608SJanet Adkins             case LedState::OFF:
364c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off;
365c9563608SJanet Adkins                 break;
366c9563608SJanet Adkins             case LedState::ON:
367c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit;
368c9563608SJanet Adkins                 break;
369c9563608SJanet Adkins             case LedState::BLINK:
370c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking;
371c9563608SJanet Adkins                 break;
372c9563608SJanet Adkins             default:
373c9563608SJanet Adkins                 break;
374c9563608SJanet Adkins         }
375c9563608SJanet Adkins     }
376c9563608SJanet Adkins }
377c9563608SJanet Adkins 
378c9563608SJanet Adkins /**
379c9563608SJanet Adkins  * @brief Builds a json sensor representation of a sensor.
380c9563608SJanet Adkins  * @param sensorName  The name of the sensor to be built
381c9563608SJanet Adkins  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
382c9563608SJanet Adkins  * build
383c9563608SJanet Adkins  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
384c9563608SJanet Adkins  * @param propertiesDict A dictionary of the properties to build the sensor
385c9563608SJanet Adkins  * from.
386c9563608SJanet Adkins  * @param sensorJson  The json object to fill
387c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
388c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
389c9563608SJanet Adkins  */
390c9563608SJanet Adkins inline void objectPropertiesToJson(
391c9563608SJanet Adkins     std::string_view sensorName, std::string_view sensorType,
3920c728b42SJanet Adkins     ChassisSubNode chassisSubNode,
393c9563608SJanet Adkins     const dbus::utility::DBusPropertiesMap& propertiesDict,
394c9563608SJanet Adkins     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
395c9563608SJanet Adkins {
396*6fe8751cSGeorge Liu     // Parameter to set to override the type we get from dbus, and force it to
397*6fe8751cSGeorge Liu     // int, regardless of what is available.  This is used for schemas like fan,
398*6fe8751cSGeorge Liu     // that require integers, not floats.
399*6fe8751cSGeorge Liu     bool forceToInt = false;
400*6fe8751cSGeorge Liu 
401*6fe8751cSGeorge Liu     nlohmann::json::json_pointer unit("/Reading");
402*6fe8751cSGeorge Liu 
403*6fe8751cSGeorge Liu     // This ChassisSubNode builds sensor excerpts
404*6fe8751cSGeorge Liu     bool isExcerpt = isExcerptNode(chassisSubNode);
405*6fe8751cSGeorge Liu 
406*6fe8751cSGeorge Liu     /* Sensor excerpts use different keys to reference the sensor. These are
407*6fe8751cSGeorge Liu      * built by the caller.
408*6fe8751cSGeorge Liu      * Additionally they don't include these additional properties.
409*6fe8751cSGeorge Liu      */
410*6fe8751cSGeorge Liu     if (!isExcerpt)
411*6fe8751cSGeorge Liu     {
4120c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
413c9563608SJanet Adkins         {
414c9563608SJanet Adkins             std::string subNodeEscaped = getSensorId(sensorName, sensorType);
415c9563608SJanet Adkins             // For sensors in SensorCollection we set Id instead of MemberId,
416c9563608SJanet Adkins             // including power sensors.
417c9563608SJanet Adkins             sensorJson["Id"] = std::move(subNodeEscaped);
418c9563608SJanet Adkins 
419c9563608SJanet Adkins             std::string sensorNameEs(sensorName);
420c9563608SJanet Adkins             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
421c9563608SJanet Adkins             sensorJson["Name"] = std::move(sensorNameEs);
422c9563608SJanet Adkins         }
423c9563608SJanet Adkins         else if (sensorType != "power")
424c9563608SJanet Adkins         {
425*6fe8751cSGeorge Liu             // Set MemberId and Name for non-power sensors.  For PowerSupplies
426*6fe8751cSGeorge Liu             // and PowerControl, those properties have more general values
427*6fe8751cSGeorge Liu             // because multiple sensors can be stored in the same JSON object.
428c9563608SJanet Adkins             std::string sensorNameEs(sensorName);
429c9563608SJanet Adkins             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
430c9563608SJanet Adkins             sensorJson["Name"] = std::move(sensorNameEs);
431c9563608SJanet Adkins         }
432c9563608SJanet Adkins 
433c9563608SJanet Adkins         const bool* checkAvailable = nullptr;
434c9563608SJanet Adkins         bool available = true;
435c9563608SJanet Adkins         const bool success = sdbusplus::unpackPropertiesNoThrow(
436c9563608SJanet Adkins             dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available",
437c9563608SJanet Adkins             checkAvailable);
438c9563608SJanet Adkins         if (!success)
439c9563608SJanet Adkins         {
440c9563608SJanet Adkins             messages::internalError();
441c9563608SJanet Adkins         }
442c9563608SJanet Adkins         if (checkAvailable != nullptr)
443c9563608SJanet Adkins         {
444c9563608SJanet Adkins             available = *checkAvailable;
445c9563608SJanet Adkins         }
446c9563608SJanet Adkins 
447c9563608SJanet Adkins         sensorJson["Status"]["State"] = getState(inventoryItem, available);
448c9563608SJanet Adkins         sensorJson["Status"]["Health"] =
449c9563608SJanet Adkins             getHealth(sensorJson, propertiesDict, inventoryItem);
450c9563608SJanet Adkins 
4510c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
452c9563608SJanet Adkins         {
453c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor";
454c9563608SJanet Adkins 
455*6fe8751cSGeorge Liu             sensor::ReadingType readingType =
456*6fe8751cSGeorge Liu                 sensors::toReadingType(sensorType);
457c9563608SJanet Adkins             if (readingType == sensor::ReadingType::Invalid)
458c9563608SJanet Adkins             {
459c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}",
460c9563608SJanet Adkins                                  sensorType);
461c9563608SJanet Adkins             }
462c9563608SJanet Adkins             else
463c9563608SJanet Adkins             {
464c9563608SJanet Adkins                 sensorJson["ReadingType"] = readingType;
465c9563608SJanet Adkins             }
466c9563608SJanet Adkins 
467c9563608SJanet Adkins             std::string_view readingUnits = sensors::toReadingUnits(sensorType);
468c9563608SJanet Adkins             if (readingUnits.empty())
469c9563608SJanet Adkins             {
470c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}",
471c9563608SJanet Adkins                                  sensorType);
472c9563608SJanet Adkins             }
473c9563608SJanet Adkins             else
474c9563608SJanet Adkins             {
475c9563608SJanet Adkins                 sensorJson["ReadingUnits"] = readingUnits;
476c9563608SJanet Adkins             }
477c9563608SJanet Adkins         }
478c9563608SJanet Adkins         else if (sensorType == "temperature")
479c9563608SJanet Adkins         {
480c9563608SJanet Adkins             unit = "/ReadingCelsius"_json_pointer;
481c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature";
482c9563608SJanet Adkins             // TODO(ed) Documentation says that path should be type fan_tach,
483c9563608SJanet Adkins             // implementation seems to implement fan
484c9563608SJanet Adkins         }
485c9563608SJanet Adkins         else if (sensorType == "fan" || sensorType == "fan_tach")
486c9563608SJanet Adkins         {
487c9563608SJanet Adkins             unit = "/Reading"_json_pointer;
488c9563608SJanet Adkins             sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM;
489c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
490c9563608SJanet Adkins             setLedState(sensorJson, inventoryItem);
491c9563608SJanet Adkins             forceToInt = true;
492c9563608SJanet Adkins         }
493c9563608SJanet Adkins         else if (sensorType == "fan_pwm")
494c9563608SJanet Adkins         {
495c9563608SJanet Adkins             unit = "/Reading"_json_pointer;
496c9563608SJanet Adkins             sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent;
497c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
498c9563608SJanet Adkins             setLedState(sensorJson, inventoryItem);
499c9563608SJanet Adkins             forceToInt = true;
500c9563608SJanet Adkins         }
501c9563608SJanet Adkins         else if (sensorType == "voltage")
502c9563608SJanet Adkins         {
503c9563608SJanet Adkins             unit = "/ReadingVolts"_json_pointer;
504c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
505c9563608SJanet Adkins         }
506c9563608SJanet Adkins         else if (sensorType == "power")
507c9563608SJanet Adkins         {
508c9563608SJanet Adkins             std::string lower;
509c9563608SJanet Adkins             std::ranges::transform(sensorName, std::back_inserter(lower),
510c9563608SJanet Adkins                                    bmcweb::asciiToLower);
511c9563608SJanet Adkins             if (lower == "total_power")
512c9563608SJanet Adkins             {
513c9563608SJanet Adkins                 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
514c9563608SJanet Adkins                 // Put multiple "sensors" into a single PowerControl, so have
515c9563608SJanet Adkins                 // generic names for MemberId and Name. Follows Redfish mockup.
516c9563608SJanet Adkins                 sensorJson["MemberId"] = "0";
517c9563608SJanet Adkins                 sensorJson["Name"] = "Chassis Power Control";
518c9563608SJanet Adkins                 unit = "/PowerConsumedWatts"_json_pointer;
519c9563608SJanet Adkins             }
520c9563608SJanet Adkins             else if (lower.find("input") != std::string::npos)
521c9563608SJanet Adkins             {
522c9563608SJanet Adkins                 unit = "/PowerInputWatts"_json_pointer;
523c9563608SJanet Adkins             }
524c9563608SJanet Adkins             else
525c9563608SJanet Adkins             {
526c9563608SJanet Adkins                 unit = "/PowerOutputWatts"_json_pointer;
527c9563608SJanet Adkins             }
528c9563608SJanet Adkins         }
529c9563608SJanet Adkins         else
530c9563608SJanet Adkins         {
531*6fe8751cSGeorge Liu             BMCWEB_LOG_ERROR("Redfish cannot map object type for {}",
532*6fe8751cSGeorge Liu                              sensorName);
533c9563608SJanet Adkins             return;
534c9563608SJanet Adkins         }
535*6fe8751cSGeorge Liu     }
536*6fe8751cSGeorge Liu 
537c9563608SJanet Adkins     // Map of dbus interface name, dbus property name and redfish property_name
538c9563608SJanet Adkins     std::vector<
539c9563608SJanet Adkins         std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
540c9563608SJanet Adkins         properties;
541c9563608SJanet Adkins 
542c9563608SJanet Adkins     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
543c9563608SJanet Adkins 
544*6fe8751cSGeorge Liu     if (!isExcerpt)
545*6fe8751cSGeorge Liu     {
5460c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
547c9563608SJanet Adkins         {
548c9563608SJanet Adkins             properties.emplace_back(
549c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
550c9563608SJanet Adkins                 "/Thresholds/UpperCaution/Reading"_json_pointer);
551c9563608SJanet Adkins             properties.emplace_back(
552c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
553c9563608SJanet Adkins                 "/Thresholds/LowerCaution/Reading"_json_pointer);
554c9563608SJanet Adkins             properties.emplace_back(
555c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
556c9563608SJanet Adkins                 "/Thresholds/UpperCritical/Reading"_json_pointer);
557c9563608SJanet Adkins             properties.emplace_back(
558c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
559c9563608SJanet Adkins                 "/Thresholds/LowerCritical/Reading"_json_pointer);
560cd5a898fSJanet Adkins 
561cd5a898fSJanet Adkins             /* Add additional properties specific to sensorType */
562cd5a898fSJanet Adkins             if (sensorType == "fan_tach")
563cd5a898fSJanet Adkins             {
564*6fe8751cSGeorge Liu                 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
565*6fe8751cSGeorge Liu                                         "Value", "/SpeedRPM"_json_pointer);
566cd5a898fSJanet Adkins             }
567c9563608SJanet Adkins         }
568c9563608SJanet Adkins         else if (sensorType != "power")
569c9563608SJanet Adkins         {
570*6fe8751cSGeorge Liu             properties.emplace_back(
571*6fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
572c9563608SJanet Adkins                 "/UpperThresholdNonCritical"_json_pointer);
573*6fe8751cSGeorge Liu             properties.emplace_back(
574*6fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
575c9563608SJanet Adkins                 "/LowerThresholdNonCritical"_json_pointer);
576*6fe8751cSGeorge Liu             properties.emplace_back(
577*6fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
578c9563608SJanet Adkins                 "/UpperThresholdCritical"_json_pointer);
579*6fe8751cSGeorge Liu             properties.emplace_back(
580*6fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
581c9563608SJanet Adkins                 "/LowerThresholdCritical"_json_pointer);
582c9563608SJanet Adkins         }
583c9563608SJanet Adkins 
584c9563608SJanet Adkins         // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
585c9563608SJanet Adkins 
5860c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
587c9563608SJanet Adkins         {
588*6fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
589*6fe8751cSGeorge Liu                                     "MinValue",
590c9563608SJanet Adkins                                     "/ReadingRangeMin"_json_pointer);
591*6fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
592*6fe8751cSGeorge Liu                                     "MaxValue",
593c9563608SJanet Adkins                                     "/ReadingRangeMax"_json_pointer);
594c9563608SJanet Adkins             properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
595c9563608SJanet Adkins                                     "Accuracy", "/Accuracy"_json_pointer);
596c9563608SJanet Adkins         }
597c9563608SJanet Adkins         else if (sensorType == "temperature")
598c9563608SJanet Adkins         {
599*6fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
600*6fe8751cSGeorge Liu                                     "MinValue",
601c9563608SJanet Adkins                                     "/MinReadingRangeTemp"_json_pointer);
602*6fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
603*6fe8751cSGeorge Liu                                     "MaxValue",
604c9563608SJanet Adkins                                     "/MaxReadingRangeTemp"_json_pointer);
605c9563608SJanet Adkins         }
606c9563608SJanet Adkins         else if (sensorType != "power")
607c9563608SJanet Adkins         {
608*6fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
609*6fe8751cSGeorge Liu                                     "MinValue",
610c9563608SJanet Adkins                                     "/MinReadingRange"_json_pointer);
611*6fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
612*6fe8751cSGeorge Liu                                     "MaxValue",
613c9563608SJanet Adkins                                     "/MaxReadingRange"_json_pointer);
614c9563608SJanet Adkins         }
615*6fe8751cSGeorge Liu     }
616c9563608SJanet Adkins 
617c9563608SJanet Adkins     for (const std::tuple<const char*, const char*,
618c9563608SJanet Adkins                           nlohmann::json::json_pointer>& p : properties)
619c9563608SJanet Adkins     {
620c9563608SJanet Adkins         for (const auto& [valueName, valueVariant] : propertiesDict)
621c9563608SJanet Adkins         {
622c9563608SJanet Adkins             if (valueName != std::get<1>(p))
623c9563608SJanet Adkins             {
624c9563608SJanet Adkins                 continue;
625c9563608SJanet Adkins             }
626c9563608SJanet Adkins 
627c9563608SJanet Adkins             // The property we want to set may be nested json, so use
628c9563608SJanet Adkins             // a json_pointer for easy indexing into the json structure.
629c9563608SJanet Adkins             const nlohmann::json::json_pointer& key = std::get<2>(p);
630c9563608SJanet Adkins 
631c9563608SJanet Adkins             const double* doubleValue = std::get_if<double>(&valueVariant);
632c9563608SJanet Adkins             if (doubleValue == nullptr)
633c9563608SJanet Adkins             {
634c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
635c9563608SJanet Adkins                 continue;
636c9563608SJanet Adkins             }
637c9563608SJanet Adkins             if (!std::isfinite(*doubleValue))
638c9563608SJanet Adkins             {
639c9563608SJanet Adkins                 if (valueName == "Value")
640c9563608SJanet Adkins                 {
641c9563608SJanet Adkins                     // Readings are allowed to be NAN for unavailable;  coerce
642c9563608SJanet Adkins                     // them to null in the json response.
643c9563608SJanet Adkins                     sensorJson[key] = nullptr;
644c9563608SJanet Adkins                     continue;
645c9563608SJanet Adkins                 }
646c9563608SJanet Adkins                 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
647c9563608SJanet Adkins                                    valueName, *doubleValue);
648c9563608SJanet Adkins                 continue;
649c9563608SJanet Adkins             }
650c9563608SJanet Adkins             if (forceToInt)
651c9563608SJanet Adkins             {
652c9563608SJanet Adkins                 sensorJson[key] = static_cast<int64_t>(*doubleValue);
653c9563608SJanet Adkins             }
654c9563608SJanet Adkins             else
655c9563608SJanet Adkins             {
656c9563608SJanet Adkins                 sensorJson[key] = *doubleValue;
657c9563608SJanet Adkins             }
658c9563608SJanet Adkins         }
659c9563608SJanet Adkins     }
660c9563608SJanet Adkins }
661c9563608SJanet Adkins 
662*6fe8751cSGeorge Liu /**
663*6fe8751cSGeorge Liu  * @brief Builds a json sensor excerpt representation of a sensor.
664*6fe8751cSGeorge Liu  *
665*6fe8751cSGeorge Liu  * @details This is a wrapper function to provide consistent setting of
666*6fe8751cSGeorge Liu  * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor
667*6fe8751cSGeorge Liu  * excerpts usually have just the D-Bus path for the sensor that is accepted
668*6fe8751cSGeorge Liu  * and used to build "DataSourceUri".
669*6fe8751cSGeorge Liu 
670*6fe8751cSGeorge Liu  * @param path The D-Bus path to the sensor to be built
671*6fe8751cSGeorge Liu  * @param chassisId The Chassis Id for the sensor
672*6fe8751cSGeorge Liu  * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor
673*6fe8751cSGeorge Liu  * @param sensorTypeExpected The expected type of the sensor
674*6fe8751cSGeorge Liu  * @param propertiesDict A dictionary of the properties to build the sensor
675*6fe8751cSGeorge Liu  * from.
676*6fe8751cSGeorge Liu  * @param sensorJson  The json object to fill
677*6fe8751cSGeorge Liu  * @returns True if sensorJson object filled. False on any error.
678*6fe8751cSGeorge Liu  * Caller is responsible for handling error.
679*6fe8751cSGeorge Liu  */
680*6fe8751cSGeorge Liu inline bool objectExcerptToJson(
681*6fe8751cSGeorge Liu     const std::string& path, const std::string_view chassisId,
682*6fe8751cSGeorge Liu     ChassisSubNode chassisSubNode,
683*6fe8751cSGeorge Liu     const std::optional<std::string>& sensorTypeExpected,
684*6fe8751cSGeorge Liu     const dbus::utility::DBusPropertiesMap& propertiesDict,
685*6fe8751cSGeorge Liu     nlohmann::json& sensorJson)
686*6fe8751cSGeorge Liu {
687*6fe8751cSGeorge Liu     if (!isExcerptNode(chassisSubNode))
688*6fe8751cSGeorge Liu     {
689*6fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("{} is not a sensor excerpt",
690*6fe8751cSGeorge Liu                          chassisSubNodeToString(chassisSubNode));
691*6fe8751cSGeorge Liu         return false;
692*6fe8751cSGeorge Liu     }
693*6fe8751cSGeorge Liu 
694*6fe8751cSGeorge Liu     sdbusplus::message::object_path sensorPath(path);
695*6fe8751cSGeorge Liu     std::string sensorName = sensorPath.filename();
696*6fe8751cSGeorge Liu     std::string sensorType = sensorPath.parent_path().filename();
697*6fe8751cSGeorge Liu     if (sensorName.empty() || sensorType.empty())
698*6fe8751cSGeorge Liu     {
699*6fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("Invalid sensor path {}", path);
700*6fe8751cSGeorge Liu         return false;
701*6fe8751cSGeorge Liu     }
702*6fe8751cSGeorge Liu 
703*6fe8751cSGeorge Liu     if (sensorTypeExpected && (sensorType != *sensorTypeExpected))
704*6fe8751cSGeorge Liu     {
705*6fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("{} is not expected type {}", path,
706*6fe8751cSGeorge Liu                          *sensorTypeExpected);
707*6fe8751cSGeorge Liu         return false;
708*6fe8751cSGeorge Liu     }
709*6fe8751cSGeorge Liu 
710*6fe8751cSGeorge Liu     // Sensor excerpts use DataSourceUri to reference full sensor Redfish path
711*6fe8751cSGeorge Liu     sensorJson["DataSourceUri"] =
712*6fe8751cSGeorge Liu         boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId,
713*6fe8751cSGeorge Liu                             getSensorId(sensorName, sensorType));
714*6fe8751cSGeorge Liu 
715*6fe8751cSGeorge Liu     // Fill in sensor excerpt properties
716*6fe8751cSGeorge Liu     objectPropertiesToJson(sensorName, sensorType, chassisSubNode,
717*6fe8751cSGeorge Liu                            propertiesDict, sensorJson, nullptr);
718*6fe8751cSGeorge Liu 
719*6fe8751cSGeorge Liu     return true;
720*6fe8751cSGeorge Liu }
721*6fe8751cSGeorge Liu 
722*6fe8751cSGeorge Liu // Maps D-Bus: Service, SensorPath
723*6fe8751cSGeorge Liu using SensorServicePathMap = std::pair<std::string, std::string>;
724*6fe8751cSGeorge Liu using SensorServicePathList = std::vector<SensorServicePathMap>;
725*6fe8751cSGeorge Liu 
726*6fe8751cSGeorge Liu inline void getAllSensorObjects(
727*6fe8751cSGeorge Liu     const std::string& associatedPath, const std::string& path,
728*6fe8751cSGeorge Liu     std::span<const std::string_view> interfaces, const int32_t depth,
729*6fe8751cSGeorge Liu     std::function<void(const boost::system::error_code& ec,
730*6fe8751cSGeorge Liu                        SensorServicePathList&)>&& callback)
731*6fe8751cSGeorge Liu {
732*6fe8751cSGeorge Liu     sdbusplus::message::object_path endpointPath{associatedPath};
733*6fe8751cSGeorge Liu     endpointPath /= "all_sensors";
734*6fe8751cSGeorge Liu 
735*6fe8751cSGeorge Liu     dbus::utility::getAssociatedSubTree(
736*6fe8751cSGeorge Liu         endpointPath, sdbusplus::message::object_path(path), depth, interfaces,
737*6fe8751cSGeorge Liu         [callback = std::move(callback)](
738*6fe8751cSGeorge Liu             const boost::system::error_code& ec,
739*6fe8751cSGeorge Liu             const dbus::utility::MapperGetSubTreeResponse& subtree) {
740*6fe8751cSGeorge Liu             SensorServicePathList sensorsServiceAndPath;
741*6fe8751cSGeorge Liu 
742*6fe8751cSGeorge Liu             if (ec)
743*6fe8751cSGeorge Liu             {
744*6fe8751cSGeorge Liu                 callback(ec, sensorsServiceAndPath);
745*6fe8751cSGeorge Liu                 return;
746*6fe8751cSGeorge Liu             }
747*6fe8751cSGeorge Liu 
748*6fe8751cSGeorge Liu             for (const auto& [sensorPath, serviceMaps] : subtree)
749*6fe8751cSGeorge Liu             {
750*6fe8751cSGeorge Liu                 for (const auto& [service, mapInterfaces] : serviceMaps)
751*6fe8751cSGeorge Liu                 {
752*6fe8751cSGeorge Liu                     sensorsServiceAndPath.emplace_back(service, sensorPath);
753*6fe8751cSGeorge Liu                 }
754*6fe8751cSGeorge Liu             }
755*6fe8751cSGeorge Liu 
756*6fe8751cSGeorge Liu             callback(ec, sensorsServiceAndPath);
757*6fe8751cSGeorge Liu         });
758*6fe8751cSGeorge Liu }
759*6fe8751cSGeorge Liu 
7601516c21bSJanet Adkins } // namespace sensor_utils
7611516c21bSJanet Adkins } // namespace redfish
762