xref: /openbmc/bmcweb/features/redfish/include/utils/sensor_utils.hpp (revision 504af5a0568171b72caf13234cc81380b261fa21)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
31516c21bSJanet Adkins #pragma once
41516c21bSJanet Adkins 
5c9563608SJanet Adkins #include "dbus_utility.hpp"
6d7857201SEd Tanous #include "error_messages.hpp"
7c9563608SJanet Adkins #include "generated/enums/resource.hpp"
8c9563608SJanet Adkins #include "generated/enums/sensor.hpp"
9c9563608SJanet Adkins #include "generated/enums/thermal.hpp"
10d7857201SEd Tanous #include "logging.hpp"
11c9563608SJanet Adkins #include "str_utility.hpp"
12c9563608SJanet Adkins #include "utils/dbus_utils.hpp"
13c9563608SJanet Adkins 
146fe8751cSGeorge Liu #include <boost/url/format.hpp>
15d7857201SEd Tanous #include <nlohmann/json.hpp>
16d7857201SEd Tanous #include <sdbusplus/message/native_types.hpp>
17c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp>
18c9563608SJanet Adkins 
191516c21bSJanet Adkins #include <algorithm>
20d7857201SEd Tanous #include <cmath>
21d7857201SEd Tanous #include <cstddef>
22d7857201SEd Tanous #include <cstdint>
231516c21bSJanet Adkins #include <format>
246fe8751cSGeorge Liu #include <functional>
25d7857201SEd Tanous #include <iterator>
266fe8751cSGeorge Liu #include <optional>
271516c21bSJanet Adkins #include <ranges>
28d7857201SEd Tanous #include <set>
29d7857201SEd Tanous #include <span>
301516c21bSJanet Adkins #include <string>
311516c21bSJanet Adkins #include <string_view>
32c9563608SJanet Adkins #include <tuple>
331516c21bSJanet Adkins #include <utility>
34d7857201SEd Tanous #include <variant>
351516c21bSJanet Adkins #include <vector>
361516c21bSJanet Adkins 
371516c21bSJanet Adkins namespace redfish
381516c21bSJanet Adkins {
391516c21bSJanet Adkins namespace sensor_utils
401516c21bSJanet Adkins {
411516c21bSJanet Adkins 
420c728b42SJanet Adkins enum class ChassisSubNode
430c728b42SJanet Adkins {
440c728b42SJanet Adkins     powerNode,
450c728b42SJanet Adkins     sensorsNode,
460c728b42SJanet Adkins     thermalNode,
476fe8751cSGeorge Liu     thermalMetricsNode,
480c728b42SJanet Adkins     unknownNode,
490c728b42SJanet Adkins };
500c728b42SJanet Adkins 
510c728b42SJanet Adkins constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode)
520c728b42SJanet Adkins {
530c728b42SJanet Adkins     switch (subNode)
540c728b42SJanet Adkins     {
550c728b42SJanet Adkins         case ChassisSubNode::powerNode:
560c728b42SJanet Adkins             return "Power";
570c728b42SJanet Adkins         case ChassisSubNode::sensorsNode:
580c728b42SJanet Adkins             return "Sensors";
590c728b42SJanet Adkins         case ChassisSubNode::thermalNode:
600c728b42SJanet Adkins             return "Thermal";
616fe8751cSGeorge Liu         case ChassisSubNode::thermalMetricsNode:
626fe8751cSGeorge Liu             return "ThermalMetrics";
630c728b42SJanet Adkins         case ChassisSubNode::unknownNode:
640c728b42SJanet Adkins         default:
650c728b42SJanet Adkins             return "";
660c728b42SJanet Adkins     }
670c728b42SJanet Adkins }
680c728b42SJanet Adkins 
690c728b42SJanet Adkins inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr)
700c728b42SJanet Adkins {
710c728b42SJanet Adkins     // If none match unknownNode is returned
720c728b42SJanet Adkins     ChassisSubNode subNode = ChassisSubNode::unknownNode;
730c728b42SJanet Adkins 
740c728b42SJanet Adkins     if (subNodeStr == "Power")
750c728b42SJanet Adkins     {
760c728b42SJanet Adkins         subNode = ChassisSubNode::powerNode;
770c728b42SJanet Adkins     }
780c728b42SJanet Adkins     else if (subNodeStr == "Sensors")
790c728b42SJanet Adkins     {
800c728b42SJanet Adkins         subNode = ChassisSubNode::sensorsNode;
810c728b42SJanet Adkins     }
820c728b42SJanet Adkins     else if (subNodeStr == "Thermal")
830c728b42SJanet Adkins     {
840c728b42SJanet Adkins         subNode = ChassisSubNode::thermalNode;
850c728b42SJanet Adkins     }
866fe8751cSGeorge Liu     else if (subNodeStr == "ThermalMetrics")
876fe8751cSGeorge Liu     {
886fe8751cSGeorge Liu         subNode = ChassisSubNode::thermalMetricsNode;
896fe8751cSGeorge Liu     }
900c728b42SJanet Adkins 
910c728b42SJanet Adkins     return subNode;
920c728b42SJanet Adkins }
93c9563608SJanet Adkins 
946fe8751cSGeorge Liu inline bool isExcerptNode(const ChassisSubNode subNode)
956fe8751cSGeorge Liu {
966fe8751cSGeorge Liu     return (subNode == ChassisSubNode::thermalMetricsNode);
976fe8751cSGeorge Liu }
986fe8751cSGeorge Liu 
99c9563608SJanet Adkins /**
100c9563608SJanet Adkins  * Possible states for physical inventory leds
101c9563608SJanet Adkins  */
102c9563608SJanet Adkins enum class LedState
103c9563608SJanet Adkins {
104c9563608SJanet Adkins     OFF,
105c9563608SJanet Adkins     ON,
106c9563608SJanet Adkins     BLINK,
107c9563608SJanet Adkins     UNKNOWN
108c9563608SJanet Adkins };
109c9563608SJanet Adkins 
110c9563608SJanet Adkins /**
111c9563608SJanet Adkins  * D-Bus inventory item associated with one or more sensors.
112c9563608SJanet Adkins  */
113c9563608SJanet Adkins class InventoryItem
114c9563608SJanet Adkins {
115c9563608SJanet Adkins   public:
116c9563608SJanet Adkins     explicit InventoryItem(const std::string& objPath) : objectPath(objPath)
117c9563608SJanet Adkins     {
118c9563608SJanet Adkins         // Set inventory item name to last node of object path
119c9563608SJanet Adkins         sdbusplus::message::object_path path(objectPath);
120c9563608SJanet Adkins         name = path.filename();
121c9563608SJanet Adkins         if (name.empty())
122c9563608SJanet Adkins         {
123c9563608SJanet Adkins             BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath);
124c9563608SJanet Adkins         }
125c9563608SJanet Adkins     }
126c9563608SJanet Adkins 
127c9563608SJanet Adkins     std::string objectPath;
128c9563608SJanet Adkins     std::string name;
129c9563608SJanet Adkins     bool isPresent = true;
130c9563608SJanet Adkins     bool isFunctional = true;
131c9563608SJanet Adkins     bool isPowerSupply = false;
132c9563608SJanet Adkins     int powerSupplyEfficiencyPercent = -1;
133c9563608SJanet Adkins     std::string manufacturer;
134c9563608SJanet Adkins     std::string model;
135c9563608SJanet Adkins     std::string partNumber;
136c9563608SJanet Adkins     std::string serialNumber;
137c9563608SJanet Adkins     std::set<std::string> sensors;
138c9563608SJanet Adkins     std::string ledObjectPath;
139c9563608SJanet Adkins     LedState ledState = LedState::UNKNOWN;
140c9563608SJanet Adkins };
141c9563608SJanet Adkins 
1421516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName,
1431516c21bSJanet Adkins                                std::string_view sensorType)
1441516c21bSJanet Adkins {
1451516c21bSJanet Adkins     std::string normalizedType(sensorType);
1461516c21bSJanet Adkins     auto remove = std::ranges::remove(normalizedType, '_');
1471516c21bSJanet Adkins     normalizedType.erase(std::ranges::begin(remove), normalizedType.end());
1481516c21bSJanet Adkins 
1491516c21bSJanet Adkins     return std::format("{}_{}", normalizedType, sensorName);
1501516c21bSJanet Adkins }
1511516c21bSJanet Adkins 
152*504af5a0SPatrick Williams inline std::pair<std::string, std::string> splitSensorNameAndType(
153*504af5a0SPatrick Williams     std::string_view sensorId)
1541516c21bSJanet Adkins {
1551516c21bSJanet Adkins     size_t index = sensorId.find('_');
1561516c21bSJanet Adkins     if (index == std::string::npos)
1571516c21bSJanet Adkins     {
1581516c21bSJanet Adkins         return std::make_pair<std::string, std::string>("", "");
1591516c21bSJanet Adkins     }
1601516c21bSJanet Adkins     std::string sensorType{sensorId.substr(0, index)};
1611516c21bSJanet Adkins     std::string sensorName{sensorId.substr(index + 1)};
1621516c21bSJanet Adkins     // fan_pwm and fan_tach need special handling
1631516c21bSJanet Adkins     if (sensorType == "fantach" || sensorType == "fanpwm")
1641516c21bSJanet Adkins     {
1651516c21bSJanet Adkins         sensorType.insert(3, 1, '_');
1661516c21bSJanet Adkins     }
1671516c21bSJanet Adkins     return std::make_pair(sensorType, sensorName);
1681516c21bSJanet Adkins }
1691516c21bSJanet Adkins 
170c9563608SJanet Adkins namespace sensors
171c9563608SJanet Adkins {
172c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType)
173c9563608SJanet Adkins {
174c9563608SJanet Adkins     if (sensorType == "voltage")
175c9563608SJanet Adkins     {
176c9563608SJanet Adkins         return "V";
177c9563608SJanet Adkins     }
178c9563608SJanet Adkins     if (sensorType == "power")
179c9563608SJanet Adkins     {
180c9563608SJanet Adkins         return "W";
181c9563608SJanet Adkins     }
182c9563608SJanet Adkins     if (sensorType == "current")
183c9563608SJanet Adkins     {
184c9563608SJanet Adkins         return "A";
185c9563608SJanet Adkins     }
186c9563608SJanet Adkins     if (sensorType == "fan_tach")
187c9563608SJanet Adkins     {
188c9563608SJanet Adkins         return "RPM";
189c9563608SJanet Adkins     }
190c9563608SJanet Adkins     if (sensorType == "temperature")
191c9563608SJanet Adkins     {
192c9563608SJanet Adkins         return "Cel";
193c9563608SJanet Adkins     }
194c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization" ||
195c9563608SJanet Adkins         sensorType == "humidity")
196c9563608SJanet Adkins     {
197c9563608SJanet Adkins         return "%";
198c9563608SJanet Adkins     }
199c9563608SJanet Adkins     if (sensorType == "altitude")
200c9563608SJanet Adkins     {
201c9563608SJanet Adkins         return "m";
202c9563608SJanet Adkins     }
203c9563608SJanet Adkins     if (sensorType == "airflow")
204c9563608SJanet Adkins     {
205c9563608SJanet Adkins         return "cft_i/min";
206c9563608SJanet Adkins     }
207c9563608SJanet Adkins     if (sensorType == "energy")
208c9563608SJanet Adkins     {
209c9563608SJanet Adkins         return "J";
210c9563608SJanet Adkins     }
211c9563608SJanet Adkins     return "";
212c9563608SJanet Adkins }
213c9563608SJanet Adkins 
214c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType)
215c9563608SJanet Adkins {
216c9563608SJanet Adkins     if (sensorType == "voltage")
217c9563608SJanet Adkins     {
218c9563608SJanet Adkins         return sensor::ReadingType::Voltage;
219c9563608SJanet Adkins     }
220c9563608SJanet Adkins     if (sensorType == "power")
221c9563608SJanet Adkins     {
222c9563608SJanet Adkins         return sensor::ReadingType::Power;
223c9563608SJanet Adkins     }
224c9563608SJanet Adkins     if (sensorType == "current")
225c9563608SJanet Adkins     {
226c9563608SJanet Adkins         return sensor::ReadingType::Current;
227c9563608SJanet Adkins     }
228c9563608SJanet Adkins     if (sensorType == "fan_tach")
229c9563608SJanet Adkins     {
230c9563608SJanet Adkins         return sensor::ReadingType::Rotational;
231c9563608SJanet Adkins     }
232c9563608SJanet Adkins     if (sensorType == "temperature")
233c9563608SJanet Adkins     {
234c9563608SJanet Adkins         return sensor::ReadingType::Temperature;
235c9563608SJanet Adkins     }
236c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization")
237c9563608SJanet Adkins     {
238c9563608SJanet Adkins         return sensor::ReadingType::Percent;
239c9563608SJanet Adkins     }
240c9563608SJanet Adkins     if (sensorType == "humidity")
241c9563608SJanet Adkins     {
242c9563608SJanet Adkins         return sensor::ReadingType::Humidity;
243c9563608SJanet Adkins     }
244c9563608SJanet Adkins     if (sensorType == "altitude")
245c9563608SJanet Adkins     {
246c9563608SJanet Adkins         return sensor::ReadingType::Altitude;
247c9563608SJanet Adkins     }
248c9563608SJanet Adkins     if (sensorType == "airflow")
249c9563608SJanet Adkins     {
250c9563608SJanet Adkins         return sensor::ReadingType::AirFlow;
251c9563608SJanet Adkins     }
252c9563608SJanet Adkins     if (sensorType == "energy")
253c9563608SJanet Adkins     {
254c9563608SJanet Adkins         return sensor::ReadingType::EnergyJoules;
255c9563608SJanet Adkins     }
256c9563608SJanet Adkins     return sensor::ReadingType::Invalid;
257c9563608SJanet Adkins }
258c9563608SJanet Adkins 
259c9563608SJanet Adkins } // namespace sensors
260c9563608SJanet Adkins 
261c9563608SJanet Adkins /**
262c9563608SJanet Adkins  * @brief Returns the Redfish State value for the specified inventory item.
263c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with a sensor.
264c9563608SJanet Adkins  * @param sensorAvailable Boolean representing if D-Bus sensor is marked as
265c9563608SJanet Adkins  * available.
266c9563608SJanet Adkins  * @return State value for inventory item.
267c9563608SJanet Adkins  */
268c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem,
269c9563608SJanet Adkins                                 const bool sensorAvailable)
270c9563608SJanet Adkins {
271c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
272c9563608SJanet Adkins     {
273c9563608SJanet Adkins         return resource::State::Absent;
274c9563608SJanet Adkins     }
275c9563608SJanet Adkins 
276c9563608SJanet Adkins     if (!sensorAvailable)
277c9563608SJanet Adkins     {
278c9563608SJanet Adkins         return resource::State::UnavailableOffline;
279c9563608SJanet Adkins     }
280c9563608SJanet Adkins 
281c9563608SJanet Adkins     return resource::State::Enabled;
282c9563608SJanet Adkins }
283c9563608SJanet Adkins 
284c9563608SJanet Adkins /**
285c9563608SJanet Adkins  * @brief Returns the Redfish Health value for the specified sensor.
286c9563608SJanet Adkins  * @param sensorJson Sensor JSON object.
287c9563608SJanet Adkins  * @param valuesDict Map of all sensor DBus values.
288c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
289c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
290c9563608SJanet Adkins  * @return Health value for sensor.
291c9563608SJanet Adkins  */
292c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson,
293c9563608SJanet Adkins                              const dbus::utility::DBusPropertiesMap& valuesDict,
294c9563608SJanet Adkins                              const InventoryItem* inventoryItem)
295c9563608SJanet Adkins {
296c9563608SJanet Adkins     // Get current health value (if any) in the sensor JSON object.  Some JSON
297c9563608SJanet Adkins     // objects contain multiple sensors (such as PowerSupplies).  We want to set
298c9563608SJanet Adkins     // the overall health to be the most severe of any of the sensors.
299c9563608SJanet Adkins     std::string currentHealth;
300c9563608SJanet Adkins     auto statusIt = sensorJson.find("Status");
301c9563608SJanet Adkins     if (statusIt != sensorJson.end())
302c9563608SJanet Adkins     {
303c9563608SJanet Adkins         auto healthIt = statusIt->find("Health");
304c9563608SJanet Adkins         if (healthIt != statusIt->end())
305c9563608SJanet Adkins         {
306c9563608SJanet Adkins             std::string* health = healthIt->get_ptr<std::string*>();
307c9563608SJanet Adkins             if (health != nullptr)
308c9563608SJanet Adkins             {
309c9563608SJanet Adkins                 currentHealth = *health;
310c9563608SJanet Adkins             }
311c9563608SJanet Adkins         }
312c9563608SJanet Adkins     }
313c9563608SJanet Adkins 
314c9563608SJanet Adkins     // If current health in JSON object is already Critical, return that.  This
315c9563608SJanet Adkins     // should override the sensor health, which might be less severe.
316c9563608SJanet Adkins     if (currentHealth == "Critical")
317c9563608SJanet Adkins     {
318c9563608SJanet Adkins         return "Critical";
319c9563608SJanet Adkins     }
320c9563608SJanet Adkins 
321c9563608SJanet Adkins     const bool* criticalAlarmHigh = nullptr;
322c9563608SJanet Adkins     const bool* criticalAlarmLow = nullptr;
323c9563608SJanet Adkins     const bool* warningAlarmHigh = nullptr;
324c9563608SJanet Adkins     const bool* warningAlarmLow = nullptr;
325c9563608SJanet Adkins 
326c9563608SJanet Adkins     const bool success = sdbusplus::unpackPropertiesNoThrow(
327c9563608SJanet Adkins         dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh",
328c9563608SJanet Adkins         criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow,
329c9563608SJanet Adkins         "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow",
330c9563608SJanet Adkins         warningAlarmLow);
331c9563608SJanet Adkins 
332c9563608SJanet Adkins     if (success)
333c9563608SJanet Adkins     {
334c9563608SJanet Adkins         // Check if sensor has critical threshold alarm
335c9563608SJanet Adkins         if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) ||
336c9563608SJanet Adkins             (criticalAlarmLow != nullptr && *criticalAlarmLow))
337c9563608SJanet Adkins         {
338c9563608SJanet Adkins             return "Critical";
339c9563608SJanet Adkins         }
340c9563608SJanet Adkins     }
341c9563608SJanet Adkins 
342c9563608SJanet Adkins     // Check if associated inventory item is not functional
343c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
344c9563608SJanet Adkins     {
345c9563608SJanet Adkins         return "Critical";
346c9563608SJanet Adkins     }
347c9563608SJanet Adkins 
348c9563608SJanet Adkins     // If current health in JSON object is already Warning, return that. This
349c9563608SJanet Adkins     // should override the sensor status, which might be less severe.
350c9563608SJanet Adkins     if (currentHealth == "Warning")
351c9563608SJanet Adkins     {
352c9563608SJanet Adkins         return "Warning";
353c9563608SJanet Adkins     }
354c9563608SJanet Adkins 
355c9563608SJanet Adkins     if (success)
356c9563608SJanet Adkins     {
357c9563608SJanet Adkins         // Check if sensor has warning threshold alarm
358c9563608SJanet Adkins         if ((warningAlarmHigh != nullptr && *warningAlarmHigh) ||
359c9563608SJanet Adkins             (warningAlarmLow != nullptr && *warningAlarmLow))
360c9563608SJanet Adkins         {
361c9563608SJanet Adkins             return "Warning";
362c9563608SJanet Adkins         }
363c9563608SJanet Adkins     }
364c9563608SJanet Adkins 
365c9563608SJanet Adkins     return "OK";
366c9563608SJanet Adkins }
367c9563608SJanet Adkins 
368c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson,
369c9563608SJanet Adkins                         const InventoryItem* inventoryItem)
370c9563608SJanet Adkins {
371c9563608SJanet Adkins     if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty())
372c9563608SJanet Adkins     {
373c9563608SJanet Adkins         switch (inventoryItem->ledState)
374c9563608SJanet Adkins         {
375c9563608SJanet Adkins             case LedState::OFF:
376c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off;
377c9563608SJanet Adkins                 break;
378c9563608SJanet Adkins             case LedState::ON:
379c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit;
380c9563608SJanet Adkins                 break;
381c9563608SJanet Adkins             case LedState::BLINK:
382c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking;
383c9563608SJanet Adkins                 break;
384c9563608SJanet Adkins             default:
385c9563608SJanet Adkins                 break;
386c9563608SJanet Adkins         }
387c9563608SJanet Adkins     }
388c9563608SJanet Adkins }
389c9563608SJanet Adkins 
390c9563608SJanet Adkins /**
391c9563608SJanet Adkins  * @brief Builds a json sensor representation of a sensor.
392c9563608SJanet Adkins  * @param sensorName  The name of the sensor to be built
393c9563608SJanet Adkins  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
394c9563608SJanet Adkins  * build
395c9563608SJanet Adkins  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
396c9563608SJanet Adkins  * @param propertiesDict A dictionary of the properties to build the sensor
397c9563608SJanet Adkins  * from.
398c9563608SJanet Adkins  * @param sensorJson  The json object to fill
399c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
400c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
401c9563608SJanet Adkins  */
402c9563608SJanet Adkins inline void objectPropertiesToJson(
403c9563608SJanet Adkins     std::string_view sensorName, std::string_view sensorType,
4040c728b42SJanet Adkins     ChassisSubNode chassisSubNode,
405c9563608SJanet Adkins     const dbus::utility::DBusPropertiesMap& propertiesDict,
406c9563608SJanet Adkins     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
407c9563608SJanet Adkins {
4086fe8751cSGeorge Liu     // Parameter to set to override the type we get from dbus, and force it to
4096fe8751cSGeorge Liu     // int, regardless of what is available.  This is used for schemas like fan,
4106fe8751cSGeorge Liu     // that require integers, not floats.
4116fe8751cSGeorge Liu     bool forceToInt = false;
4126fe8751cSGeorge Liu 
4136fe8751cSGeorge Liu     nlohmann::json::json_pointer unit("/Reading");
4146fe8751cSGeorge Liu 
4156fe8751cSGeorge Liu     // This ChassisSubNode builds sensor excerpts
4166fe8751cSGeorge Liu     bool isExcerpt = isExcerptNode(chassisSubNode);
4176fe8751cSGeorge Liu 
4186fe8751cSGeorge Liu     /* Sensor excerpts use different keys to reference the sensor. These are
4196fe8751cSGeorge Liu      * built by the caller.
4206fe8751cSGeorge Liu      * Additionally they don't include these additional properties.
4216fe8751cSGeorge Liu      */
4226fe8751cSGeorge Liu     if (!isExcerpt)
4236fe8751cSGeorge Liu     {
4240c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
425c9563608SJanet Adkins         {
426c9563608SJanet Adkins             std::string subNodeEscaped = getSensorId(sensorName, sensorType);
427c9563608SJanet Adkins             // For sensors in SensorCollection we set Id instead of MemberId,
428c9563608SJanet Adkins             // including power sensors.
429c9563608SJanet Adkins             sensorJson["Id"] = std::move(subNodeEscaped);
430c9563608SJanet Adkins 
431c9563608SJanet Adkins             std::string sensorNameEs(sensorName);
432c9563608SJanet Adkins             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
433c9563608SJanet Adkins             sensorJson["Name"] = std::move(sensorNameEs);
434c9563608SJanet Adkins         }
435c9563608SJanet Adkins         else if (sensorType != "power")
436c9563608SJanet Adkins         {
4376fe8751cSGeorge Liu             // Set MemberId and Name for non-power sensors.  For PowerSupplies
4386fe8751cSGeorge Liu             // and PowerControl, those properties have more general values
4396fe8751cSGeorge Liu             // because multiple sensors can be stored in the same JSON object.
440c9563608SJanet Adkins             std::string sensorNameEs(sensorName);
441c9563608SJanet Adkins             std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
442c9563608SJanet Adkins             sensorJson["Name"] = std::move(sensorNameEs);
443c9563608SJanet Adkins         }
444c9563608SJanet Adkins 
445c9563608SJanet Adkins         const bool* checkAvailable = nullptr;
446c9563608SJanet Adkins         bool available = true;
447c9563608SJanet Adkins         const bool success = sdbusplus::unpackPropertiesNoThrow(
448c9563608SJanet Adkins             dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available",
449c9563608SJanet Adkins             checkAvailable);
450c9563608SJanet Adkins         if (!success)
451c9563608SJanet Adkins         {
452c9563608SJanet Adkins             messages::internalError();
453c9563608SJanet Adkins         }
454c9563608SJanet Adkins         if (checkAvailable != nullptr)
455c9563608SJanet Adkins         {
456c9563608SJanet Adkins             available = *checkAvailable;
457c9563608SJanet Adkins         }
458c9563608SJanet Adkins 
459c9563608SJanet Adkins         sensorJson["Status"]["State"] = getState(inventoryItem, available);
460c9563608SJanet Adkins         sensorJson["Status"]["Health"] =
461c9563608SJanet Adkins             getHealth(sensorJson, propertiesDict, inventoryItem);
462c9563608SJanet Adkins 
4630c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
464c9563608SJanet Adkins         {
465c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor";
466c9563608SJanet Adkins 
4676fe8751cSGeorge Liu             sensor::ReadingType readingType =
4686fe8751cSGeorge Liu                 sensors::toReadingType(sensorType);
469c9563608SJanet Adkins             if (readingType == sensor::ReadingType::Invalid)
470c9563608SJanet Adkins             {
471c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}",
472c9563608SJanet Adkins                                  sensorType);
473c9563608SJanet Adkins             }
474c9563608SJanet Adkins             else
475c9563608SJanet Adkins             {
476c9563608SJanet Adkins                 sensorJson["ReadingType"] = readingType;
477c9563608SJanet Adkins             }
478c9563608SJanet Adkins 
479c9563608SJanet Adkins             std::string_view readingUnits = sensors::toReadingUnits(sensorType);
480c9563608SJanet Adkins             if (readingUnits.empty())
481c9563608SJanet Adkins             {
482c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}",
483c9563608SJanet Adkins                                  sensorType);
484c9563608SJanet Adkins             }
485c9563608SJanet Adkins             else
486c9563608SJanet Adkins             {
487c9563608SJanet Adkins                 sensorJson["ReadingUnits"] = readingUnits;
488c9563608SJanet Adkins             }
489c9563608SJanet Adkins         }
490c9563608SJanet Adkins         else if (sensorType == "temperature")
491c9563608SJanet Adkins         {
492c9563608SJanet Adkins             unit = "/ReadingCelsius"_json_pointer;
493c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature";
494c9563608SJanet Adkins             // TODO(ed) Documentation says that path should be type fan_tach,
495c9563608SJanet Adkins             // implementation seems to implement fan
496c9563608SJanet Adkins         }
497c9563608SJanet Adkins         else if (sensorType == "fan" || sensorType == "fan_tach")
498c9563608SJanet Adkins         {
499c9563608SJanet Adkins             unit = "/Reading"_json_pointer;
500c9563608SJanet Adkins             sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM;
501c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
502c9563608SJanet Adkins             setLedState(sensorJson, inventoryItem);
503c9563608SJanet Adkins             forceToInt = true;
504c9563608SJanet Adkins         }
505c9563608SJanet Adkins         else if (sensorType == "fan_pwm")
506c9563608SJanet Adkins         {
507c9563608SJanet Adkins             unit = "/Reading"_json_pointer;
508c9563608SJanet Adkins             sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent;
509c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
510c9563608SJanet Adkins             setLedState(sensorJson, inventoryItem);
511c9563608SJanet Adkins             forceToInt = true;
512c9563608SJanet Adkins         }
513c9563608SJanet Adkins         else if (sensorType == "voltage")
514c9563608SJanet Adkins         {
515c9563608SJanet Adkins             unit = "/ReadingVolts"_json_pointer;
516c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
517c9563608SJanet Adkins         }
518c9563608SJanet Adkins         else if (sensorType == "power")
519c9563608SJanet Adkins         {
520c9563608SJanet Adkins             std::string lower;
521c9563608SJanet Adkins             std::ranges::transform(sensorName, std::back_inserter(lower),
522c9563608SJanet Adkins                                    bmcweb::asciiToLower);
523c9563608SJanet Adkins             if (lower == "total_power")
524c9563608SJanet Adkins             {
525c9563608SJanet Adkins                 sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
526c9563608SJanet Adkins                 // Put multiple "sensors" into a single PowerControl, so have
527c9563608SJanet Adkins                 // generic names for MemberId and Name. Follows Redfish mockup.
528c9563608SJanet Adkins                 sensorJson["MemberId"] = "0";
529c9563608SJanet Adkins                 sensorJson["Name"] = "Chassis Power Control";
530c9563608SJanet Adkins                 unit = "/PowerConsumedWatts"_json_pointer;
531c9563608SJanet Adkins             }
532c9563608SJanet Adkins             else if (lower.find("input") != std::string::npos)
533c9563608SJanet Adkins             {
534c9563608SJanet Adkins                 unit = "/PowerInputWatts"_json_pointer;
535c9563608SJanet Adkins             }
536c9563608SJanet Adkins             else
537c9563608SJanet Adkins             {
538c9563608SJanet Adkins                 unit = "/PowerOutputWatts"_json_pointer;
539c9563608SJanet Adkins             }
540c9563608SJanet Adkins         }
541c9563608SJanet Adkins         else
542c9563608SJanet Adkins         {
5436fe8751cSGeorge Liu             BMCWEB_LOG_ERROR("Redfish cannot map object type for {}",
5446fe8751cSGeorge Liu                              sensorName);
545c9563608SJanet Adkins             return;
546c9563608SJanet Adkins         }
5476fe8751cSGeorge Liu     }
5486fe8751cSGeorge Liu 
549c9563608SJanet Adkins     // Map of dbus interface name, dbus property name and redfish property_name
550c9563608SJanet Adkins     std::vector<
551c9563608SJanet Adkins         std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
552c9563608SJanet Adkins         properties;
553c9563608SJanet Adkins 
554c9563608SJanet Adkins     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
555c9563608SJanet Adkins 
5566fe8751cSGeorge Liu     if (!isExcerpt)
5576fe8751cSGeorge Liu     {
5580c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
559c9563608SJanet Adkins         {
560c9563608SJanet Adkins             properties.emplace_back(
561c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
562c9563608SJanet Adkins                 "/Thresholds/UpperCaution/Reading"_json_pointer);
563c9563608SJanet Adkins             properties.emplace_back(
564c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
565c9563608SJanet Adkins                 "/Thresholds/LowerCaution/Reading"_json_pointer);
566c9563608SJanet Adkins             properties.emplace_back(
567c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
568c9563608SJanet Adkins                 "/Thresholds/UpperCritical/Reading"_json_pointer);
569c9563608SJanet Adkins             properties.emplace_back(
570c9563608SJanet Adkins                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
571c9563608SJanet Adkins                 "/Thresholds/LowerCritical/Reading"_json_pointer);
572cd5a898fSJanet Adkins 
573cd5a898fSJanet Adkins             /* Add additional properties specific to sensorType */
574cd5a898fSJanet Adkins             if (sensorType == "fan_tach")
575cd5a898fSJanet Adkins             {
5766fe8751cSGeorge Liu                 properties.emplace_back("xyz.openbmc_project.Sensor.Value",
5776fe8751cSGeorge Liu                                         "Value", "/SpeedRPM"_json_pointer);
578cd5a898fSJanet Adkins             }
579c9563608SJanet Adkins         }
580c9563608SJanet Adkins         else if (sensorType != "power")
581c9563608SJanet Adkins         {
5826fe8751cSGeorge Liu             properties.emplace_back(
5836fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
584c9563608SJanet Adkins                 "/UpperThresholdNonCritical"_json_pointer);
5856fe8751cSGeorge Liu             properties.emplace_back(
5866fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
587c9563608SJanet Adkins                 "/LowerThresholdNonCritical"_json_pointer);
5886fe8751cSGeorge Liu             properties.emplace_back(
5896fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
590c9563608SJanet Adkins                 "/UpperThresholdCritical"_json_pointer);
5916fe8751cSGeorge Liu             properties.emplace_back(
5926fe8751cSGeorge Liu                 "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
593c9563608SJanet Adkins                 "/LowerThresholdCritical"_json_pointer);
594c9563608SJanet Adkins         }
595c9563608SJanet Adkins 
596c9563608SJanet Adkins         // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
597c9563608SJanet Adkins 
5980c728b42SJanet Adkins         if (chassisSubNode == ChassisSubNode::sensorsNode)
599c9563608SJanet Adkins         {
6006fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6016fe8751cSGeorge Liu                                     "MinValue",
602c9563608SJanet Adkins                                     "/ReadingRangeMin"_json_pointer);
6036fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6046fe8751cSGeorge Liu                                     "MaxValue",
605c9563608SJanet Adkins                                     "/ReadingRangeMax"_json_pointer);
606c9563608SJanet Adkins             properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
607c9563608SJanet Adkins                                     "Accuracy", "/Accuracy"_json_pointer);
608c9563608SJanet Adkins         }
609c9563608SJanet Adkins         else if (sensorType == "temperature")
610c9563608SJanet Adkins         {
6116fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6126fe8751cSGeorge Liu                                     "MinValue",
613c9563608SJanet Adkins                                     "/MinReadingRangeTemp"_json_pointer);
6146fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6156fe8751cSGeorge Liu                                     "MaxValue",
616c9563608SJanet Adkins                                     "/MaxReadingRangeTemp"_json_pointer);
617c9563608SJanet Adkins         }
618c9563608SJanet Adkins         else if (sensorType != "power")
619c9563608SJanet Adkins         {
6206fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6216fe8751cSGeorge Liu                                     "MinValue",
622c9563608SJanet Adkins                                     "/MinReadingRange"_json_pointer);
6236fe8751cSGeorge Liu             properties.emplace_back("xyz.openbmc_project.Sensor.Value",
6246fe8751cSGeorge Liu                                     "MaxValue",
625c9563608SJanet Adkins                                     "/MaxReadingRange"_json_pointer);
626c9563608SJanet Adkins         }
6276fe8751cSGeorge Liu     }
628c9563608SJanet Adkins 
629c9563608SJanet Adkins     for (const std::tuple<const char*, const char*,
630c9563608SJanet Adkins                           nlohmann::json::json_pointer>& p : properties)
631c9563608SJanet Adkins     {
632c9563608SJanet Adkins         for (const auto& [valueName, valueVariant] : propertiesDict)
633c9563608SJanet Adkins         {
634c9563608SJanet Adkins             if (valueName != std::get<1>(p))
635c9563608SJanet Adkins             {
636c9563608SJanet Adkins                 continue;
637c9563608SJanet Adkins             }
638c9563608SJanet Adkins 
639c9563608SJanet Adkins             // The property we want to set may be nested json, so use
640c9563608SJanet Adkins             // a json_pointer for easy indexing into the json structure.
641c9563608SJanet Adkins             const nlohmann::json::json_pointer& key = std::get<2>(p);
642c9563608SJanet Adkins 
643c9563608SJanet Adkins             const double* doubleValue = std::get_if<double>(&valueVariant);
644c9563608SJanet Adkins             if (doubleValue == nullptr)
645c9563608SJanet Adkins             {
646c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
647c9563608SJanet Adkins                 continue;
648c9563608SJanet Adkins             }
649c9563608SJanet Adkins             if (!std::isfinite(*doubleValue))
650c9563608SJanet Adkins             {
651c9563608SJanet Adkins                 if (valueName == "Value")
652c9563608SJanet Adkins                 {
653c9563608SJanet Adkins                     // Readings are allowed to be NAN for unavailable;  coerce
654c9563608SJanet Adkins                     // them to null in the json response.
655c9563608SJanet Adkins                     sensorJson[key] = nullptr;
656c9563608SJanet Adkins                     continue;
657c9563608SJanet Adkins                 }
658c9563608SJanet Adkins                 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
659c9563608SJanet Adkins                                    valueName, *doubleValue);
660c9563608SJanet Adkins                 continue;
661c9563608SJanet Adkins             }
662c9563608SJanet Adkins             if (forceToInt)
663c9563608SJanet Adkins             {
664c9563608SJanet Adkins                 sensorJson[key] = static_cast<int64_t>(*doubleValue);
665c9563608SJanet Adkins             }
666c9563608SJanet Adkins             else
667c9563608SJanet Adkins             {
668c9563608SJanet Adkins                 sensorJson[key] = *doubleValue;
669c9563608SJanet Adkins             }
670c9563608SJanet Adkins         }
671c9563608SJanet Adkins     }
672c9563608SJanet Adkins }
673c9563608SJanet Adkins 
6746fe8751cSGeorge Liu /**
6756fe8751cSGeorge Liu  * @brief Builds a json sensor excerpt representation of a sensor.
6766fe8751cSGeorge Liu  *
6776fe8751cSGeorge Liu  * @details This is a wrapper function to provide consistent setting of
6786fe8751cSGeorge Liu  * "DataSourceUri" for sensor excerpts and filling of properties. Since sensor
6796fe8751cSGeorge Liu  * excerpts usually have just the D-Bus path for the sensor that is accepted
6806fe8751cSGeorge Liu  * and used to build "DataSourceUri".
6816fe8751cSGeorge Liu 
6826fe8751cSGeorge Liu  * @param path The D-Bus path to the sensor to be built
6836fe8751cSGeorge Liu  * @param chassisId The Chassis Id for the sensor
6846fe8751cSGeorge Liu  * @param chassisSubNode The subnode (e.g. ThermalMetrics) of the sensor
6856fe8751cSGeorge Liu  * @param sensorTypeExpected The expected type of the sensor
6866fe8751cSGeorge Liu  * @param propertiesDict A dictionary of the properties to build the sensor
6876fe8751cSGeorge Liu  * from.
6886fe8751cSGeorge Liu  * @param sensorJson  The json object to fill
6896fe8751cSGeorge Liu  * @returns True if sensorJson object filled. False on any error.
6906fe8751cSGeorge Liu  * Caller is responsible for handling error.
6916fe8751cSGeorge Liu  */
6926fe8751cSGeorge Liu inline bool objectExcerptToJson(
6936fe8751cSGeorge Liu     const std::string& path, const std::string_view chassisId,
6946fe8751cSGeorge Liu     ChassisSubNode chassisSubNode,
6956fe8751cSGeorge Liu     const std::optional<std::string>& sensorTypeExpected,
6966fe8751cSGeorge Liu     const dbus::utility::DBusPropertiesMap& propertiesDict,
6976fe8751cSGeorge Liu     nlohmann::json& sensorJson)
6986fe8751cSGeorge Liu {
6996fe8751cSGeorge Liu     if (!isExcerptNode(chassisSubNode))
7006fe8751cSGeorge Liu     {
7016fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("{} is not a sensor excerpt",
7026fe8751cSGeorge Liu                          chassisSubNodeToString(chassisSubNode));
7036fe8751cSGeorge Liu         return false;
7046fe8751cSGeorge Liu     }
7056fe8751cSGeorge Liu 
7066fe8751cSGeorge Liu     sdbusplus::message::object_path sensorPath(path);
7076fe8751cSGeorge Liu     std::string sensorName = sensorPath.filename();
7086fe8751cSGeorge Liu     std::string sensorType = sensorPath.parent_path().filename();
7096fe8751cSGeorge Liu     if (sensorName.empty() || sensorType.empty())
7106fe8751cSGeorge Liu     {
7116fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("Invalid sensor path {}", path);
7126fe8751cSGeorge Liu         return false;
7136fe8751cSGeorge Liu     }
7146fe8751cSGeorge Liu 
7156fe8751cSGeorge Liu     if (sensorTypeExpected && (sensorType != *sensorTypeExpected))
7166fe8751cSGeorge Liu     {
7176fe8751cSGeorge Liu         BMCWEB_LOG_DEBUG("{} is not expected type {}", path,
7186fe8751cSGeorge Liu                          *sensorTypeExpected);
7196fe8751cSGeorge Liu         return false;
7206fe8751cSGeorge Liu     }
7216fe8751cSGeorge Liu 
7226fe8751cSGeorge Liu     // Sensor excerpts use DataSourceUri to reference full sensor Redfish path
7236fe8751cSGeorge Liu     sensorJson["DataSourceUri"] =
7246fe8751cSGeorge Liu         boost::urls::format("/redfish/v1/Chassis/{}/Sensors/{}", chassisId,
7256fe8751cSGeorge Liu                             getSensorId(sensorName, sensorType));
7266fe8751cSGeorge Liu 
7276fe8751cSGeorge Liu     // Fill in sensor excerpt properties
7286fe8751cSGeorge Liu     objectPropertiesToJson(sensorName, sensorType, chassisSubNode,
7296fe8751cSGeorge Liu                            propertiesDict, sensorJson, nullptr);
7306fe8751cSGeorge Liu 
7316fe8751cSGeorge Liu     return true;
7326fe8751cSGeorge Liu }
7336fe8751cSGeorge Liu 
7346fe8751cSGeorge Liu // Maps D-Bus: Service, SensorPath
7356fe8751cSGeorge Liu using SensorServicePathMap = std::pair<std::string, std::string>;
7366fe8751cSGeorge Liu using SensorServicePathList = std::vector<SensorServicePathMap>;
7376fe8751cSGeorge Liu 
7386fe8751cSGeorge Liu inline void getAllSensorObjects(
7396fe8751cSGeorge Liu     const std::string& associatedPath, const std::string& path,
7406fe8751cSGeorge Liu     std::span<const std::string_view> interfaces, const int32_t depth,
7416fe8751cSGeorge Liu     std::function<void(const boost::system::error_code& ec,
7426fe8751cSGeorge Liu                        SensorServicePathList&)>&& callback)
7436fe8751cSGeorge Liu {
7446fe8751cSGeorge Liu     sdbusplus::message::object_path endpointPath{associatedPath};
7456fe8751cSGeorge Liu     endpointPath /= "all_sensors";
7466fe8751cSGeorge Liu 
7476fe8751cSGeorge Liu     dbus::utility::getAssociatedSubTree(
7486fe8751cSGeorge Liu         endpointPath, sdbusplus::message::object_path(path), depth, interfaces,
7496fe8751cSGeorge Liu         [callback = std::move(callback)](
7506fe8751cSGeorge Liu             const boost::system::error_code& ec,
7516fe8751cSGeorge Liu             const dbus::utility::MapperGetSubTreeResponse& subtree) {
7526fe8751cSGeorge Liu             SensorServicePathList sensorsServiceAndPath;
7536fe8751cSGeorge Liu 
7546fe8751cSGeorge Liu             if (ec)
7556fe8751cSGeorge Liu             {
7566fe8751cSGeorge Liu                 callback(ec, sensorsServiceAndPath);
7576fe8751cSGeorge Liu                 return;
7586fe8751cSGeorge Liu             }
7596fe8751cSGeorge Liu 
7606fe8751cSGeorge Liu             for (const auto& [sensorPath, serviceMaps] : subtree)
7616fe8751cSGeorge Liu             {
7626fe8751cSGeorge Liu                 for (const auto& [service, mapInterfaces] : serviceMaps)
7636fe8751cSGeorge Liu                 {
7646fe8751cSGeorge Liu                     sensorsServiceAndPath.emplace_back(service, sensorPath);
7656fe8751cSGeorge Liu                 }
7666fe8751cSGeorge Liu             }
7676fe8751cSGeorge Liu 
7686fe8751cSGeorge Liu             callback(ec, sensorsServiceAndPath);
7696fe8751cSGeorge Liu         });
7706fe8751cSGeorge Liu }
7716fe8751cSGeorge Liu 
7721516c21bSJanet Adkins } // namespace sensor_utils
7731516c21bSJanet Adkins } // namespace redfish
774