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 
11c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp>
12c9563608SJanet Adkins 
131516c21bSJanet Adkins #include <algorithm>
141516c21bSJanet Adkins #include <format>
151516c21bSJanet Adkins #include <ranges>
161516c21bSJanet Adkins #include <string>
171516c21bSJanet Adkins #include <string_view>
18c9563608SJanet Adkins #include <tuple>
191516c21bSJanet Adkins #include <utility>
201516c21bSJanet Adkins #include <vector>
211516c21bSJanet Adkins 
221516c21bSJanet Adkins namespace redfish
231516c21bSJanet Adkins {
241516c21bSJanet Adkins namespace sensor_utils
251516c21bSJanet Adkins {
261516c21bSJanet Adkins 
270c728b42SJanet Adkins enum class ChassisSubNode
280c728b42SJanet Adkins {
290c728b42SJanet Adkins     powerNode,
300c728b42SJanet Adkins     sensorsNode,
310c728b42SJanet Adkins     thermalNode,
320c728b42SJanet Adkins     unknownNode,
330c728b42SJanet Adkins };
340c728b42SJanet Adkins 
chassisSubNodeToString(ChassisSubNode subNode)350c728b42SJanet Adkins constexpr std::string_view chassisSubNodeToString(ChassisSubNode subNode)
360c728b42SJanet Adkins {
370c728b42SJanet Adkins     switch (subNode)
380c728b42SJanet Adkins     {
390c728b42SJanet Adkins         case ChassisSubNode::powerNode:
400c728b42SJanet Adkins             return "Power";
410c728b42SJanet Adkins         case ChassisSubNode::sensorsNode:
420c728b42SJanet Adkins             return "Sensors";
430c728b42SJanet Adkins         case ChassisSubNode::thermalNode:
440c728b42SJanet Adkins             return "Thermal";
450c728b42SJanet Adkins         case ChassisSubNode::unknownNode:
460c728b42SJanet Adkins         default:
470c728b42SJanet Adkins             return "";
480c728b42SJanet Adkins     }
490c728b42SJanet Adkins }
500c728b42SJanet Adkins 
chassisSubNodeFromString(const std::string & subNodeStr)510c728b42SJanet Adkins inline ChassisSubNode chassisSubNodeFromString(const std::string& subNodeStr)
520c728b42SJanet Adkins {
530c728b42SJanet Adkins     // If none match unknownNode is returned
540c728b42SJanet Adkins     ChassisSubNode subNode = ChassisSubNode::unknownNode;
550c728b42SJanet Adkins 
560c728b42SJanet Adkins     if (subNodeStr == "Power")
570c728b42SJanet Adkins     {
580c728b42SJanet Adkins         subNode = ChassisSubNode::powerNode;
590c728b42SJanet Adkins     }
600c728b42SJanet Adkins     else if (subNodeStr == "Sensors")
610c728b42SJanet Adkins     {
620c728b42SJanet Adkins         subNode = ChassisSubNode::sensorsNode;
630c728b42SJanet Adkins     }
640c728b42SJanet Adkins     else if (subNodeStr == "Thermal")
650c728b42SJanet Adkins     {
660c728b42SJanet Adkins         subNode = ChassisSubNode::thermalNode;
670c728b42SJanet Adkins     }
680c728b42SJanet Adkins 
690c728b42SJanet Adkins     return subNode;
700c728b42SJanet Adkins }
71c9563608SJanet Adkins 
72c9563608SJanet Adkins /**
73c9563608SJanet Adkins  * Possible states for physical inventory leds
74c9563608SJanet Adkins  */
75c9563608SJanet Adkins enum class LedState
76c9563608SJanet Adkins {
77c9563608SJanet Adkins     OFF,
78c9563608SJanet Adkins     ON,
79c9563608SJanet Adkins     BLINK,
80c9563608SJanet Adkins     UNKNOWN
81c9563608SJanet Adkins };
82c9563608SJanet Adkins 
83c9563608SJanet Adkins /**
84c9563608SJanet Adkins  * D-Bus inventory item associated with one or more sensors.
85c9563608SJanet Adkins  */
86c9563608SJanet Adkins class InventoryItem
87c9563608SJanet Adkins {
88c9563608SJanet Adkins   public:
InventoryItem(const std::string & objPath)89c9563608SJanet Adkins     explicit InventoryItem(const std::string& objPath) : objectPath(objPath)
90c9563608SJanet Adkins     {
91c9563608SJanet Adkins         // Set inventory item name to last node of object path
92c9563608SJanet Adkins         sdbusplus::message::object_path path(objectPath);
93c9563608SJanet Adkins         name = path.filename();
94c9563608SJanet Adkins         if (name.empty())
95c9563608SJanet Adkins         {
96c9563608SJanet Adkins             BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath);
97c9563608SJanet Adkins         }
98c9563608SJanet Adkins     }
99c9563608SJanet Adkins 
100c9563608SJanet Adkins     std::string objectPath;
101c9563608SJanet Adkins     std::string name;
102c9563608SJanet Adkins     bool isPresent = true;
103c9563608SJanet Adkins     bool isFunctional = true;
104c9563608SJanet Adkins     bool isPowerSupply = false;
105c9563608SJanet Adkins     int powerSupplyEfficiencyPercent = -1;
106c9563608SJanet Adkins     std::string manufacturer;
107c9563608SJanet Adkins     std::string model;
108c9563608SJanet Adkins     std::string partNumber;
109c9563608SJanet Adkins     std::string serialNumber;
110c9563608SJanet Adkins     std::set<std::string> sensors;
111c9563608SJanet Adkins     std::string ledObjectPath;
112c9563608SJanet Adkins     LedState ledState = LedState::UNKNOWN;
113c9563608SJanet Adkins };
114c9563608SJanet Adkins 
getSensorId(std::string_view sensorName,std::string_view sensorType)1151516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName,
1161516c21bSJanet Adkins                                std::string_view sensorType)
1171516c21bSJanet Adkins {
1181516c21bSJanet Adkins     std::string normalizedType(sensorType);
1191516c21bSJanet Adkins     auto remove = std::ranges::remove(normalizedType, '_');
1201516c21bSJanet Adkins     normalizedType.erase(std::ranges::begin(remove), normalizedType.end());
1211516c21bSJanet Adkins 
1221516c21bSJanet Adkins     return std::format("{}_{}", normalizedType, sensorName);
1231516c21bSJanet Adkins }
1241516c21bSJanet Adkins 
1251516c21bSJanet Adkins inline std::pair<std::string, std::string>
splitSensorNameAndType(std::string_view sensorId)1261516c21bSJanet Adkins     splitSensorNameAndType(std::string_view sensorId)
1271516c21bSJanet Adkins {
1281516c21bSJanet Adkins     size_t index = sensorId.find('_');
1291516c21bSJanet Adkins     if (index == std::string::npos)
1301516c21bSJanet Adkins     {
1311516c21bSJanet Adkins         return std::make_pair<std::string, std::string>("", "");
1321516c21bSJanet Adkins     }
1331516c21bSJanet Adkins     std::string sensorType{sensorId.substr(0, index)};
1341516c21bSJanet Adkins     std::string sensorName{sensorId.substr(index + 1)};
1351516c21bSJanet Adkins     // fan_pwm and fan_tach need special handling
1361516c21bSJanet Adkins     if (sensorType == "fantach" || sensorType == "fanpwm")
1371516c21bSJanet Adkins     {
1381516c21bSJanet Adkins         sensorType.insert(3, 1, '_');
1391516c21bSJanet Adkins     }
1401516c21bSJanet Adkins     return std::make_pair(sensorType, sensorName);
1411516c21bSJanet Adkins }
1421516c21bSJanet Adkins 
143c9563608SJanet Adkins namespace sensors
144c9563608SJanet Adkins {
toReadingUnits(std::string_view sensorType)145c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType)
146c9563608SJanet Adkins {
147c9563608SJanet Adkins     if (sensorType == "voltage")
148c9563608SJanet Adkins     {
149c9563608SJanet Adkins         return "V";
150c9563608SJanet Adkins     }
151c9563608SJanet Adkins     if (sensorType == "power")
152c9563608SJanet Adkins     {
153c9563608SJanet Adkins         return "W";
154c9563608SJanet Adkins     }
155c9563608SJanet Adkins     if (sensorType == "current")
156c9563608SJanet Adkins     {
157c9563608SJanet Adkins         return "A";
158c9563608SJanet Adkins     }
159c9563608SJanet Adkins     if (sensorType == "fan_tach")
160c9563608SJanet Adkins     {
161c9563608SJanet Adkins         return "RPM";
162c9563608SJanet Adkins     }
163c9563608SJanet Adkins     if (sensorType == "temperature")
164c9563608SJanet Adkins     {
165c9563608SJanet Adkins         return "Cel";
166c9563608SJanet Adkins     }
167c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization" ||
168c9563608SJanet Adkins         sensorType == "humidity")
169c9563608SJanet Adkins     {
170c9563608SJanet Adkins         return "%";
171c9563608SJanet Adkins     }
172c9563608SJanet Adkins     if (sensorType == "altitude")
173c9563608SJanet Adkins     {
174c9563608SJanet Adkins         return "m";
175c9563608SJanet Adkins     }
176c9563608SJanet Adkins     if (sensorType == "airflow")
177c9563608SJanet Adkins     {
178c9563608SJanet Adkins         return "cft_i/min";
179c9563608SJanet Adkins     }
180c9563608SJanet Adkins     if (sensorType == "energy")
181c9563608SJanet Adkins     {
182c9563608SJanet Adkins         return "J";
183c9563608SJanet Adkins     }
184c9563608SJanet Adkins     return "";
185c9563608SJanet Adkins }
186c9563608SJanet Adkins 
toReadingType(std::string_view sensorType)187c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType)
188c9563608SJanet Adkins {
189c9563608SJanet Adkins     if (sensorType == "voltage")
190c9563608SJanet Adkins     {
191c9563608SJanet Adkins         return sensor::ReadingType::Voltage;
192c9563608SJanet Adkins     }
193c9563608SJanet Adkins     if (sensorType == "power")
194c9563608SJanet Adkins     {
195c9563608SJanet Adkins         return sensor::ReadingType::Power;
196c9563608SJanet Adkins     }
197c9563608SJanet Adkins     if (sensorType == "current")
198c9563608SJanet Adkins     {
199c9563608SJanet Adkins         return sensor::ReadingType::Current;
200c9563608SJanet Adkins     }
201c9563608SJanet Adkins     if (sensorType == "fan_tach")
202c9563608SJanet Adkins     {
203c9563608SJanet Adkins         return sensor::ReadingType::Rotational;
204c9563608SJanet Adkins     }
205c9563608SJanet Adkins     if (sensorType == "temperature")
206c9563608SJanet Adkins     {
207c9563608SJanet Adkins         return sensor::ReadingType::Temperature;
208c9563608SJanet Adkins     }
209c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization")
210c9563608SJanet Adkins     {
211c9563608SJanet Adkins         return sensor::ReadingType::Percent;
212c9563608SJanet Adkins     }
213c9563608SJanet Adkins     if (sensorType == "humidity")
214c9563608SJanet Adkins     {
215c9563608SJanet Adkins         return sensor::ReadingType::Humidity;
216c9563608SJanet Adkins     }
217c9563608SJanet Adkins     if (sensorType == "altitude")
218c9563608SJanet Adkins     {
219c9563608SJanet Adkins         return sensor::ReadingType::Altitude;
220c9563608SJanet Adkins     }
221c9563608SJanet Adkins     if (sensorType == "airflow")
222c9563608SJanet Adkins     {
223c9563608SJanet Adkins         return sensor::ReadingType::AirFlow;
224c9563608SJanet Adkins     }
225c9563608SJanet Adkins     if (sensorType == "energy")
226c9563608SJanet Adkins     {
227c9563608SJanet Adkins         return sensor::ReadingType::EnergyJoules;
228c9563608SJanet Adkins     }
229c9563608SJanet Adkins     return sensor::ReadingType::Invalid;
230c9563608SJanet Adkins }
231c9563608SJanet Adkins 
232c9563608SJanet Adkins } // namespace sensors
233c9563608SJanet Adkins 
234c9563608SJanet Adkins /**
235c9563608SJanet Adkins  * @brief Returns the Redfish State value for the specified inventory item.
236c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with a sensor.
237c9563608SJanet Adkins  * @param sensorAvailable Boolean representing if D-Bus sensor is marked as
238c9563608SJanet Adkins  * available.
239c9563608SJanet Adkins  * @return State value for inventory item.
240c9563608SJanet Adkins  */
getState(const InventoryItem * inventoryItem,const bool sensorAvailable)241c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem,
242c9563608SJanet Adkins                                 const bool sensorAvailable)
243c9563608SJanet Adkins {
244c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
245c9563608SJanet Adkins     {
246c9563608SJanet Adkins         return resource::State::Absent;
247c9563608SJanet Adkins     }
248c9563608SJanet Adkins 
249c9563608SJanet Adkins     if (!sensorAvailable)
250c9563608SJanet Adkins     {
251c9563608SJanet Adkins         return resource::State::UnavailableOffline;
252c9563608SJanet Adkins     }
253c9563608SJanet Adkins 
254c9563608SJanet Adkins     return resource::State::Enabled;
255c9563608SJanet Adkins }
256c9563608SJanet Adkins 
257c9563608SJanet Adkins /**
258c9563608SJanet Adkins  * @brief Returns the Redfish Health value for the specified sensor.
259c9563608SJanet Adkins  * @param sensorJson Sensor JSON object.
260c9563608SJanet Adkins  * @param valuesDict Map of all sensor DBus values.
261c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
262c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
263c9563608SJanet Adkins  * @return Health value for sensor.
264c9563608SJanet Adkins  */
getHealth(nlohmann::json & sensorJson,const dbus::utility::DBusPropertiesMap & valuesDict,const InventoryItem * inventoryItem)265c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson,
266c9563608SJanet Adkins                              const dbus::utility::DBusPropertiesMap& valuesDict,
267c9563608SJanet Adkins                              const InventoryItem* inventoryItem)
268c9563608SJanet Adkins {
269c9563608SJanet Adkins     // Get current health value (if any) in the sensor JSON object.  Some JSON
270c9563608SJanet Adkins     // objects contain multiple sensors (such as PowerSupplies).  We want to set
271c9563608SJanet Adkins     // the overall health to be the most severe of any of the sensors.
272c9563608SJanet Adkins     std::string currentHealth;
273c9563608SJanet Adkins     auto statusIt = sensorJson.find("Status");
274c9563608SJanet Adkins     if (statusIt != sensorJson.end())
275c9563608SJanet Adkins     {
276c9563608SJanet Adkins         auto healthIt = statusIt->find("Health");
277c9563608SJanet Adkins         if (healthIt != statusIt->end())
278c9563608SJanet Adkins         {
279c9563608SJanet Adkins             std::string* health = healthIt->get_ptr<std::string*>();
280c9563608SJanet Adkins             if (health != nullptr)
281c9563608SJanet Adkins             {
282c9563608SJanet Adkins                 currentHealth = *health;
283c9563608SJanet Adkins             }
284c9563608SJanet Adkins         }
285c9563608SJanet Adkins     }
286c9563608SJanet Adkins 
287c9563608SJanet Adkins     // If current health in JSON object is already Critical, return that.  This
288c9563608SJanet Adkins     // should override the sensor health, which might be less severe.
289c9563608SJanet Adkins     if (currentHealth == "Critical")
290c9563608SJanet Adkins     {
291c9563608SJanet Adkins         return "Critical";
292c9563608SJanet Adkins     }
293c9563608SJanet Adkins 
294c9563608SJanet Adkins     const bool* criticalAlarmHigh = nullptr;
295c9563608SJanet Adkins     const bool* criticalAlarmLow = nullptr;
296c9563608SJanet Adkins     const bool* warningAlarmHigh = nullptr;
297c9563608SJanet Adkins     const bool* warningAlarmLow = nullptr;
298c9563608SJanet Adkins 
299c9563608SJanet Adkins     const bool success = sdbusplus::unpackPropertiesNoThrow(
300c9563608SJanet Adkins         dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh",
301c9563608SJanet Adkins         criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow,
302c9563608SJanet Adkins         "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow",
303c9563608SJanet Adkins         warningAlarmLow);
304c9563608SJanet Adkins 
305c9563608SJanet Adkins     if (success)
306c9563608SJanet Adkins     {
307c9563608SJanet Adkins         // Check if sensor has critical threshold alarm
308c9563608SJanet Adkins         if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) ||
309c9563608SJanet Adkins             (criticalAlarmLow != nullptr && *criticalAlarmLow))
310c9563608SJanet Adkins         {
311c9563608SJanet Adkins             return "Critical";
312c9563608SJanet Adkins         }
313c9563608SJanet Adkins     }
314c9563608SJanet Adkins 
315c9563608SJanet Adkins     // Check if associated inventory item is not functional
316c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
317c9563608SJanet Adkins     {
318c9563608SJanet Adkins         return "Critical";
319c9563608SJanet Adkins     }
320c9563608SJanet Adkins 
321c9563608SJanet Adkins     // If current health in JSON object is already Warning, return that. This
322c9563608SJanet Adkins     // should override the sensor status, which might be less severe.
323c9563608SJanet Adkins     if (currentHealth == "Warning")
324c9563608SJanet Adkins     {
325c9563608SJanet Adkins         return "Warning";
326c9563608SJanet Adkins     }
327c9563608SJanet Adkins 
328c9563608SJanet Adkins     if (success)
329c9563608SJanet Adkins     {
330c9563608SJanet Adkins         // Check if sensor has warning threshold alarm
331c9563608SJanet Adkins         if ((warningAlarmHigh != nullptr && *warningAlarmHigh) ||
332c9563608SJanet Adkins             (warningAlarmLow != nullptr && *warningAlarmLow))
333c9563608SJanet Adkins         {
334c9563608SJanet Adkins             return "Warning";
335c9563608SJanet Adkins         }
336c9563608SJanet Adkins     }
337c9563608SJanet Adkins 
338c9563608SJanet Adkins     return "OK";
339c9563608SJanet Adkins }
340c9563608SJanet Adkins 
setLedState(nlohmann::json & sensorJson,const InventoryItem * inventoryItem)341c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson,
342c9563608SJanet Adkins                         const InventoryItem* inventoryItem)
343c9563608SJanet Adkins {
344c9563608SJanet Adkins     if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty())
345c9563608SJanet Adkins     {
346c9563608SJanet Adkins         switch (inventoryItem->ledState)
347c9563608SJanet Adkins         {
348c9563608SJanet Adkins             case LedState::OFF:
349c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off;
350c9563608SJanet Adkins                 break;
351c9563608SJanet Adkins             case LedState::ON:
352c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit;
353c9563608SJanet Adkins                 break;
354c9563608SJanet Adkins             case LedState::BLINK:
355c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking;
356c9563608SJanet Adkins                 break;
357c9563608SJanet Adkins             default:
358c9563608SJanet Adkins                 break;
359c9563608SJanet Adkins         }
360c9563608SJanet Adkins     }
361c9563608SJanet Adkins }
362c9563608SJanet Adkins 
363c9563608SJanet Adkins /**
364c9563608SJanet Adkins  * @brief Builds a json sensor representation of a sensor.
365c9563608SJanet Adkins  * @param sensorName  The name of the sensor to be built
366c9563608SJanet Adkins  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
367c9563608SJanet Adkins  * build
368c9563608SJanet Adkins  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
369c9563608SJanet Adkins  * @param propertiesDict A dictionary of the properties to build the sensor
370c9563608SJanet Adkins  * from.
371c9563608SJanet Adkins  * @param sensorJson  The json object to fill
372c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
373c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
374c9563608SJanet Adkins  */
objectPropertiesToJson(std::string_view sensorName,std::string_view sensorType,ChassisSubNode chassisSubNode,const dbus::utility::DBusPropertiesMap & propertiesDict,nlohmann::json & sensorJson,InventoryItem * inventoryItem)375c9563608SJanet Adkins inline void objectPropertiesToJson(
376c9563608SJanet Adkins     std::string_view sensorName, std::string_view sensorType,
3770c728b42SJanet Adkins     ChassisSubNode chassisSubNode,
378c9563608SJanet Adkins     const dbus::utility::DBusPropertiesMap& propertiesDict,
379c9563608SJanet Adkins     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
380c9563608SJanet Adkins {
3810c728b42SJanet Adkins     if (chassisSubNode == ChassisSubNode::sensorsNode)
382c9563608SJanet Adkins     {
383c9563608SJanet Adkins         std::string subNodeEscaped = getSensorId(sensorName, sensorType);
384c9563608SJanet Adkins         // For sensors in SensorCollection we set Id instead of MemberId,
385c9563608SJanet Adkins         // including power sensors.
386c9563608SJanet Adkins         sensorJson["Id"] = std::move(subNodeEscaped);
387c9563608SJanet Adkins 
388c9563608SJanet Adkins         std::string sensorNameEs(sensorName);
389c9563608SJanet Adkins         std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
390c9563608SJanet Adkins         sensorJson["Name"] = std::move(sensorNameEs);
391c9563608SJanet Adkins     }
392c9563608SJanet Adkins     else if (sensorType != "power")
393c9563608SJanet Adkins     {
394c9563608SJanet Adkins         // Set MemberId and Name for non-power sensors.  For PowerSupplies and
395c9563608SJanet Adkins         // PowerControl, those properties have more general values because
396c9563608SJanet Adkins         // multiple sensors can be stored in the same JSON object.
397c9563608SJanet Adkins         std::string sensorNameEs(sensorName);
398c9563608SJanet Adkins         std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
399c9563608SJanet Adkins         sensorJson["Name"] = std::move(sensorNameEs);
400c9563608SJanet Adkins     }
401c9563608SJanet Adkins 
402c9563608SJanet Adkins     const bool* checkAvailable = nullptr;
403c9563608SJanet Adkins     bool available = true;
404c9563608SJanet Adkins     const bool success = sdbusplus::unpackPropertiesNoThrow(
405c9563608SJanet Adkins         dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available",
406c9563608SJanet Adkins         checkAvailable);
407c9563608SJanet Adkins     if (!success)
408c9563608SJanet Adkins     {
409c9563608SJanet Adkins         messages::internalError();
410c9563608SJanet Adkins     }
411c9563608SJanet Adkins     if (checkAvailable != nullptr)
412c9563608SJanet Adkins     {
413c9563608SJanet Adkins         available = *checkAvailable;
414c9563608SJanet Adkins     }
415c9563608SJanet Adkins 
416c9563608SJanet Adkins     sensorJson["Status"]["State"] = getState(inventoryItem, available);
417c9563608SJanet Adkins     sensorJson["Status"]["Health"] =
418c9563608SJanet Adkins         getHealth(sensorJson, propertiesDict, inventoryItem);
419c9563608SJanet Adkins 
420c9563608SJanet Adkins     // Parameter to set to override the type we get from dbus, and force it to
421c9563608SJanet Adkins     // int, regardless of what is available.  This is used for schemas like fan,
422c9563608SJanet Adkins     // that require integers, not floats.
423c9563608SJanet Adkins     bool forceToInt = false;
424c9563608SJanet Adkins 
425c9563608SJanet Adkins     nlohmann::json::json_pointer unit("/Reading");
4260c728b42SJanet Adkins     if (chassisSubNode == ChassisSubNode::sensorsNode)
427c9563608SJanet Adkins     {
428c9563608SJanet Adkins         sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor";
429c9563608SJanet Adkins 
430c9563608SJanet Adkins         sensor::ReadingType readingType = sensors::toReadingType(sensorType);
431c9563608SJanet Adkins         if (readingType == sensor::ReadingType::Invalid)
432c9563608SJanet Adkins         {
433c9563608SJanet Adkins             BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}",
434c9563608SJanet Adkins                              sensorType);
435c9563608SJanet Adkins         }
436c9563608SJanet Adkins         else
437c9563608SJanet Adkins         {
438c9563608SJanet Adkins             sensorJson["ReadingType"] = readingType;
439c9563608SJanet Adkins         }
440c9563608SJanet Adkins 
441c9563608SJanet Adkins         std::string_view readingUnits = sensors::toReadingUnits(sensorType);
442c9563608SJanet Adkins         if (readingUnits.empty())
443c9563608SJanet Adkins         {
444c9563608SJanet Adkins             BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}",
445c9563608SJanet Adkins                              sensorType);
446c9563608SJanet Adkins         }
447c9563608SJanet Adkins         else
448c9563608SJanet Adkins         {
449c9563608SJanet Adkins             sensorJson["ReadingUnits"] = readingUnits;
450c9563608SJanet Adkins         }
451c9563608SJanet Adkins     }
452c9563608SJanet Adkins     else if (sensorType == "temperature")
453c9563608SJanet Adkins     {
454c9563608SJanet Adkins         unit = "/ReadingCelsius"_json_pointer;
455c9563608SJanet Adkins         sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature";
456c9563608SJanet Adkins         // TODO(ed) Documentation says that path should be type fan_tach,
457c9563608SJanet Adkins         // implementation seems to implement fan
458c9563608SJanet Adkins     }
459c9563608SJanet Adkins     else if (sensorType == "fan" || sensorType == "fan_tach")
460c9563608SJanet Adkins     {
461c9563608SJanet Adkins         unit = "/Reading"_json_pointer;
462c9563608SJanet Adkins         sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM;
463c9563608SJanet Adkins         sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
464c9563608SJanet Adkins         setLedState(sensorJson, inventoryItem);
465c9563608SJanet Adkins         forceToInt = true;
466c9563608SJanet Adkins     }
467c9563608SJanet Adkins     else if (sensorType == "fan_pwm")
468c9563608SJanet Adkins     {
469c9563608SJanet Adkins         unit = "/Reading"_json_pointer;
470c9563608SJanet Adkins         sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent;
471c9563608SJanet Adkins         sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
472c9563608SJanet Adkins         setLedState(sensorJson, inventoryItem);
473c9563608SJanet Adkins         forceToInt = true;
474c9563608SJanet Adkins     }
475c9563608SJanet Adkins     else if (sensorType == "voltage")
476c9563608SJanet Adkins     {
477c9563608SJanet Adkins         unit = "/ReadingVolts"_json_pointer;
478c9563608SJanet Adkins         sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
479c9563608SJanet Adkins     }
480c9563608SJanet Adkins     else if (sensorType == "power")
481c9563608SJanet Adkins     {
482c9563608SJanet Adkins         std::string lower;
483c9563608SJanet Adkins         std::ranges::transform(sensorName, std::back_inserter(lower),
484c9563608SJanet Adkins                                bmcweb::asciiToLower);
485c9563608SJanet Adkins         if (lower == "total_power")
486c9563608SJanet Adkins         {
487c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
488c9563608SJanet Adkins             // Put multiple "sensors" into a single PowerControl, so have
489c9563608SJanet Adkins             // generic names for MemberId and Name. Follows Redfish mockup.
490c9563608SJanet Adkins             sensorJson["MemberId"] = "0";
491c9563608SJanet Adkins             sensorJson["Name"] = "Chassis Power Control";
492c9563608SJanet Adkins             unit = "/PowerConsumedWatts"_json_pointer;
493c9563608SJanet Adkins         }
494c9563608SJanet Adkins         else if (lower.find("input") != std::string::npos)
495c9563608SJanet Adkins         {
496c9563608SJanet Adkins             unit = "/PowerInputWatts"_json_pointer;
497c9563608SJanet Adkins         }
498c9563608SJanet Adkins         else
499c9563608SJanet Adkins         {
500c9563608SJanet Adkins             unit = "/PowerOutputWatts"_json_pointer;
501c9563608SJanet Adkins         }
502c9563608SJanet Adkins     }
503c9563608SJanet Adkins     else
504c9563608SJanet Adkins     {
505c9563608SJanet Adkins         BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", sensorName);
506c9563608SJanet Adkins         return;
507c9563608SJanet Adkins     }
508c9563608SJanet Adkins     // Map of dbus interface name, dbus property name and redfish property_name
509c9563608SJanet Adkins     std::vector<
510c9563608SJanet Adkins         std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
511c9563608SJanet Adkins         properties;
512c9563608SJanet Adkins 
513c9563608SJanet Adkins     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
514c9563608SJanet Adkins 
5150c728b42SJanet Adkins     if (chassisSubNode == ChassisSubNode::sensorsNode)
516c9563608SJanet Adkins     {
517c9563608SJanet Adkins         properties.emplace_back(
518c9563608SJanet Adkins             "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
519c9563608SJanet Adkins             "/Thresholds/UpperCaution/Reading"_json_pointer);
520c9563608SJanet Adkins         properties.emplace_back(
521c9563608SJanet Adkins             "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
522c9563608SJanet Adkins             "/Thresholds/LowerCaution/Reading"_json_pointer);
523c9563608SJanet Adkins         properties.emplace_back(
524c9563608SJanet Adkins             "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
525c9563608SJanet Adkins             "/Thresholds/UpperCritical/Reading"_json_pointer);
526c9563608SJanet Adkins         properties.emplace_back(
527c9563608SJanet Adkins             "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
528c9563608SJanet Adkins             "/Thresholds/LowerCritical/Reading"_json_pointer);
529*cd5a898fSJanet Adkins 
530*cd5a898fSJanet Adkins         /* Add additional properties specific to sensorType */
531*cd5a898fSJanet Adkins         if (sensorType == "fan_tach")
532*cd5a898fSJanet Adkins         {
533*cd5a898fSJanet Adkins             properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value",
534*cd5a898fSJanet Adkins                                     "/SpeedRPM"_json_pointer);
535*cd5a898fSJanet Adkins         }
536c9563608SJanet Adkins     }
537c9563608SJanet Adkins     else if (sensorType != "power")
538c9563608SJanet Adkins     {
539c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
540c9563608SJanet Adkins                                 "WarningHigh",
541c9563608SJanet Adkins                                 "/UpperThresholdNonCritical"_json_pointer);
542c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
543c9563608SJanet Adkins                                 "WarningLow",
544c9563608SJanet Adkins                                 "/LowerThresholdNonCritical"_json_pointer);
545c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
546c9563608SJanet Adkins                                 "CriticalHigh",
547c9563608SJanet Adkins                                 "/UpperThresholdCritical"_json_pointer);
548c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
549c9563608SJanet Adkins                                 "CriticalLow",
550c9563608SJanet Adkins                                 "/LowerThresholdCritical"_json_pointer);
551c9563608SJanet Adkins     }
552c9563608SJanet Adkins 
553c9563608SJanet Adkins     // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
554c9563608SJanet Adkins 
5550c728b42SJanet Adkins     if (chassisSubNode == ChassisSubNode::sensorsNode)
556c9563608SJanet Adkins     {
557c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
558c9563608SJanet Adkins                                 "/ReadingRangeMin"_json_pointer);
559c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
560c9563608SJanet Adkins                                 "/ReadingRangeMax"_json_pointer);
561c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
562c9563608SJanet Adkins                                 "Accuracy", "/Accuracy"_json_pointer);
563c9563608SJanet Adkins     }
564c9563608SJanet Adkins     else if (sensorType == "temperature")
565c9563608SJanet Adkins     {
566c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
567c9563608SJanet Adkins                                 "/MinReadingRangeTemp"_json_pointer);
568c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
569c9563608SJanet Adkins                                 "/MaxReadingRangeTemp"_json_pointer);
570c9563608SJanet Adkins     }
571c9563608SJanet Adkins     else if (sensorType != "power")
572c9563608SJanet Adkins     {
573c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
574c9563608SJanet Adkins                                 "/MinReadingRange"_json_pointer);
575c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
576c9563608SJanet Adkins                                 "/MaxReadingRange"_json_pointer);
577c9563608SJanet Adkins     }
578c9563608SJanet Adkins 
579c9563608SJanet Adkins     for (const std::tuple<const char*, const char*,
580c9563608SJanet Adkins                           nlohmann::json::json_pointer>& p : properties)
581c9563608SJanet Adkins     {
582c9563608SJanet Adkins         for (const auto& [valueName, valueVariant] : propertiesDict)
583c9563608SJanet Adkins         {
584c9563608SJanet Adkins             if (valueName != std::get<1>(p))
585c9563608SJanet Adkins             {
586c9563608SJanet Adkins                 continue;
587c9563608SJanet Adkins             }
588c9563608SJanet Adkins 
589c9563608SJanet Adkins             // The property we want to set may be nested json, so use
590c9563608SJanet Adkins             // a json_pointer for easy indexing into the json structure.
591c9563608SJanet Adkins             const nlohmann::json::json_pointer& key = std::get<2>(p);
592c9563608SJanet Adkins 
593c9563608SJanet Adkins             const double* doubleValue = std::get_if<double>(&valueVariant);
594c9563608SJanet Adkins             if (doubleValue == nullptr)
595c9563608SJanet Adkins             {
596c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
597c9563608SJanet Adkins                 continue;
598c9563608SJanet Adkins             }
599c9563608SJanet Adkins             if (!std::isfinite(*doubleValue))
600c9563608SJanet Adkins             {
601c9563608SJanet Adkins                 if (valueName == "Value")
602c9563608SJanet Adkins                 {
603c9563608SJanet Adkins                     // Readings are allowed to be NAN for unavailable;  coerce
604c9563608SJanet Adkins                     // them to null in the json response.
605c9563608SJanet Adkins                     sensorJson[key] = nullptr;
606c9563608SJanet Adkins                     continue;
607c9563608SJanet Adkins                 }
608c9563608SJanet Adkins                 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
609c9563608SJanet Adkins                                    valueName, *doubleValue);
610c9563608SJanet Adkins                 continue;
611c9563608SJanet Adkins             }
612c9563608SJanet Adkins             if (forceToInt)
613c9563608SJanet Adkins             {
614c9563608SJanet Adkins                 sensorJson[key] = static_cast<int64_t>(*doubleValue);
615c9563608SJanet Adkins             }
616c9563608SJanet Adkins             else
617c9563608SJanet Adkins             {
618c9563608SJanet Adkins                 sensorJson[key] = *doubleValue;
619c9563608SJanet Adkins             }
620c9563608SJanet Adkins         }
621c9563608SJanet Adkins     }
622c9563608SJanet Adkins }
623c9563608SJanet Adkins 
6241516c21bSJanet Adkins } // namespace sensor_utils
6251516c21bSJanet Adkins } // namespace redfish
626