xref: /openbmc/bmcweb/features/redfish/include/utils/sensor_utils.hpp (revision c95636082b63d020e0f63545f5d87e2eb6a05c52)
11516c21bSJanet Adkins #pragma once
21516c21bSJanet Adkins 
3*c9563608SJanet Adkins #include "dbus_utility.hpp"
4*c9563608SJanet Adkins #include "generated/enums/resource.hpp"
5*c9563608SJanet Adkins #include "generated/enums/sensor.hpp"
6*c9563608SJanet Adkins #include "generated/enums/thermal.hpp"
7*c9563608SJanet Adkins #include "str_utility.hpp"
8*c9563608SJanet Adkins #include "utils/dbus_utils.hpp"
9*c9563608SJanet Adkins #include "utils/json_utils.hpp"
10*c9563608SJanet Adkins 
11*c9563608SJanet Adkins #include <sdbusplus/unpack_properties.hpp>
12*c9563608SJanet Adkins 
131516c21bSJanet Adkins #include <algorithm>
141516c21bSJanet Adkins #include <format>
151516c21bSJanet Adkins #include <ranges>
161516c21bSJanet Adkins #include <string>
171516c21bSJanet Adkins #include <string_view>
18*c9563608SJanet 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 
27*c9563608SJanet Adkins static constexpr std::string_view powerNode = "Power";
28*c9563608SJanet Adkins static constexpr std::string_view sensorsNode = "Sensors";
29*c9563608SJanet Adkins static constexpr std::string_view thermalNode = "Thermal";
30*c9563608SJanet Adkins 
31*c9563608SJanet Adkins /**
32*c9563608SJanet Adkins  * Possible states for physical inventory leds
33*c9563608SJanet Adkins  */
34*c9563608SJanet Adkins enum class LedState
35*c9563608SJanet Adkins {
36*c9563608SJanet Adkins     OFF,
37*c9563608SJanet Adkins     ON,
38*c9563608SJanet Adkins     BLINK,
39*c9563608SJanet Adkins     UNKNOWN
40*c9563608SJanet Adkins };
41*c9563608SJanet Adkins 
42*c9563608SJanet Adkins /**
43*c9563608SJanet Adkins  * D-Bus inventory item associated with one or more sensors.
44*c9563608SJanet Adkins  */
45*c9563608SJanet Adkins class InventoryItem
46*c9563608SJanet Adkins {
47*c9563608SJanet Adkins   public:
48*c9563608SJanet Adkins     explicit InventoryItem(const std::string& objPath) : objectPath(objPath)
49*c9563608SJanet Adkins     {
50*c9563608SJanet Adkins         // Set inventory item name to last node of object path
51*c9563608SJanet Adkins         sdbusplus::message::object_path path(objectPath);
52*c9563608SJanet Adkins         name = path.filename();
53*c9563608SJanet Adkins         if (name.empty())
54*c9563608SJanet Adkins         {
55*c9563608SJanet Adkins             BMCWEB_LOG_ERROR("Failed to find '/' in {}", objectPath);
56*c9563608SJanet Adkins         }
57*c9563608SJanet Adkins     }
58*c9563608SJanet Adkins 
59*c9563608SJanet Adkins     std::string objectPath;
60*c9563608SJanet Adkins     std::string name;
61*c9563608SJanet Adkins     bool isPresent = true;
62*c9563608SJanet Adkins     bool isFunctional = true;
63*c9563608SJanet Adkins     bool isPowerSupply = false;
64*c9563608SJanet Adkins     int powerSupplyEfficiencyPercent = -1;
65*c9563608SJanet Adkins     std::string manufacturer;
66*c9563608SJanet Adkins     std::string model;
67*c9563608SJanet Adkins     std::string partNumber;
68*c9563608SJanet Adkins     std::string serialNumber;
69*c9563608SJanet Adkins     std::set<std::string> sensors;
70*c9563608SJanet Adkins     std::string ledObjectPath;
71*c9563608SJanet Adkins     LedState ledState = LedState::UNKNOWN;
72*c9563608SJanet Adkins };
73*c9563608SJanet Adkins 
741516c21bSJanet Adkins inline std::string getSensorId(std::string_view sensorName,
751516c21bSJanet Adkins                                std::string_view sensorType)
761516c21bSJanet Adkins {
771516c21bSJanet Adkins     std::string normalizedType(sensorType);
781516c21bSJanet Adkins     auto remove = std::ranges::remove(normalizedType, '_');
791516c21bSJanet Adkins     normalizedType.erase(std::ranges::begin(remove), normalizedType.end());
801516c21bSJanet Adkins 
811516c21bSJanet Adkins     return std::format("{}_{}", normalizedType, sensorName);
821516c21bSJanet Adkins }
831516c21bSJanet Adkins 
841516c21bSJanet Adkins inline std::pair<std::string, std::string>
851516c21bSJanet Adkins     splitSensorNameAndType(std::string_view sensorId)
861516c21bSJanet Adkins {
871516c21bSJanet Adkins     size_t index = sensorId.find('_');
881516c21bSJanet Adkins     if (index == std::string::npos)
891516c21bSJanet Adkins     {
901516c21bSJanet Adkins         return std::make_pair<std::string, std::string>("", "");
911516c21bSJanet Adkins     }
921516c21bSJanet Adkins     std::string sensorType{sensorId.substr(0, index)};
931516c21bSJanet Adkins     std::string sensorName{sensorId.substr(index + 1)};
941516c21bSJanet Adkins     // fan_pwm and fan_tach need special handling
951516c21bSJanet Adkins     if (sensorType == "fantach" || sensorType == "fanpwm")
961516c21bSJanet Adkins     {
971516c21bSJanet Adkins         sensorType.insert(3, 1, '_');
981516c21bSJanet Adkins     }
991516c21bSJanet Adkins     return std::make_pair(sensorType, sensorName);
1001516c21bSJanet Adkins }
1011516c21bSJanet Adkins 
102*c9563608SJanet Adkins namespace sensors
103*c9563608SJanet Adkins {
104*c9563608SJanet Adkins inline std::string_view toReadingUnits(std::string_view sensorType)
105*c9563608SJanet Adkins {
106*c9563608SJanet Adkins     if (sensorType == "voltage")
107*c9563608SJanet Adkins     {
108*c9563608SJanet Adkins         return "V";
109*c9563608SJanet Adkins     }
110*c9563608SJanet Adkins     if (sensorType == "power")
111*c9563608SJanet Adkins     {
112*c9563608SJanet Adkins         return "W";
113*c9563608SJanet Adkins     }
114*c9563608SJanet Adkins     if (sensorType == "current")
115*c9563608SJanet Adkins     {
116*c9563608SJanet Adkins         return "A";
117*c9563608SJanet Adkins     }
118*c9563608SJanet Adkins     if (sensorType == "fan_tach")
119*c9563608SJanet Adkins     {
120*c9563608SJanet Adkins         return "RPM";
121*c9563608SJanet Adkins     }
122*c9563608SJanet Adkins     if (sensorType == "temperature")
123*c9563608SJanet Adkins     {
124*c9563608SJanet Adkins         return "Cel";
125*c9563608SJanet Adkins     }
126*c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization" ||
127*c9563608SJanet Adkins         sensorType == "humidity")
128*c9563608SJanet Adkins     {
129*c9563608SJanet Adkins         return "%";
130*c9563608SJanet Adkins     }
131*c9563608SJanet Adkins     if (sensorType == "altitude")
132*c9563608SJanet Adkins     {
133*c9563608SJanet Adkins         return "m";
134*c9563608SJanet Adkins     }
135*c9563608SJanet Adkins     if (sensorType == "airflow")
136*c9563608SJanet Adkins     {
137*c9563608SJanet Adkins         return "cft_i/min";
138*c9563608SJanet Adkins     }
139*c9563608SJanet Adkins     if (sensorType == "energy")
140*c9563608SJanet Adkins     {
141*c9563608SJanet Adkins         return "J";
142*c9563608SJanet Adkins     }
143*c9563608SJanet Adkins     return "";
144*c9563608SJanet Adkins }
145*c9563608SJanet Adkins 
146*c9563608SJanet Adkins inline sensor::ReadingType toReadingType(std::string_view sensorType)
147*c9563608SJanet Adkins {
148*c9563608SJanet Adkins     if (sensorType == "voltage")
149*c9563608SJanet Adkins     {
150*c9563608SJanet Adkins         return sensor::ReadingType::Voltage;
151*c9563608SJanet Adkins     }
152*c9563608SJanet Adkins     if (sensorType == "power")
153*c9563608SJanet Adkins     {
154*c9563608SJanet Adkins         return sensor::ReadingType::Power;
155*c9563608SJanet Adkins     }
156*c9563608SJanet Adkins     if (sensorType == "current")
157*c9563608SJanet Adkins     {
158*c9563608SJanet Adkins         return sensor::ReadingType::Current;
159*c9563608SJanet Adkins     }
160*c9563608SJanet Adkins     if (sensorType == "fan_tach")
161*c9563608SJanet Adkins     {
162*c9563608SJanet Adkins         return sensor::ReadingType::Rotational;
163*c9563608SJanet Adkins     }
164*c9563608SJanet Adkins     if (sensorType == "temperature")
165*c9563608SJanet Adkins     {
166*c9563608SJanet Adkins         return sensor::ReadingType::Temperature;
167*c9563608SJanet Adkins     }
168*c9563608SJanet Adkins     if (sensorType == "fan_pwm" || sensorType == "utilization")
169*c9563608SJanet Adkins     {
170*c9563608SJanet Adkins         return sensor::ReadingType::Percent;
171*c9563608SJanet Adkins     }
172*c9563608SJanet Adkins     if (sensorType == "humidity")
173*c9563608SJanet Adkins     {
174*c9563608SJanet Adkins         return sensor::ReadingType::Humidity;
175*c9563608SJanet Adkins     }
176*c9563608SJanet Adkins     if (sensorType == "altitude")
177*c9563608SJanet Adkins     {
178*c9563608SJanet Adkins         return sensor::ReadingType::Altitude;
179*c9563608SJanet Adkins     }
180*c9563608SJanet Adkins     if (sensorType == "airflow")
181*c9563608SJanet Adkins     {
182*c9563608SJanet Adkins         return sensor::ReadingType::AirFlow;
183*c9563608SJanet Adkins     }
184*c9563608SJanet Adkins     if (sensorType == "energy")
185*c9563608SJanet Adkins     {
186*c9563608SJanet Adkins         return sensor::ReadingType::EnergyJoules;
187*c9563608SJanet Adkins     }
188*c9563608SJanet Adkins     return sensor::ReadingType::Invalid;
189*c9563608SJanet Adkins }
190*c9563608SJanet Adkins 
191*c9563608SJanet Adkins } // namespace sensors
192*c9563608SJanet Adkins 
193*c9563608SJanet Adkins /**
194*c9563608SJanet Adkins  * @brief Returns the Redfish State value for the specified inventory item.
195*c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with a sensor.
196*c9563608SJanet Adkins  * @param sensorAvailable Boolean representing if D-Bus sensor is marked as
197*c9563608SJanet Adkins  * available.
198*c9563608SJanet Adkins  * @return State value for inventory item.
199*c9563608SJanet Adkins  */
200*c9563608SJanet Adkins inline resource::State getState(const InventoryItem* inventoryItem,
201*c9563608SJanet Adkins                                 const bool sensorAvailable)
202*c9563608SJanet Adkins {
203*c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
204*c9563608SJanet Adkins     {
205*c9563608SJanet Adkins         return resource::State::Absent;
206*c9563608SJanet Adkins     }
207*c9563608SJanet Adkins 
208*c9563608SJanet Adkins     if (!sensorAvailable)
209*c9563608SJanet Adkins     {
210*c9563608SJanet Adkins         return resource::State::UnavailableOffline;
211*c9563608SJanet Adkins     }
212*c9563608SJanet Adkins 
213*c9563608SJanet Adkins     return resource::State::Enabled;
214*c9563608SJanet Adkins }
215*c9563608SJanet Adkins 
216*c9563608SJanet Adkins /**
217*c9563608SJanet Adkins  * @brief Returns the Redfish Health value for the specified sensor.
218*c9563608SJanet Adkins  * @param sensorJson Sensor JSON object.
219*c9563608SJanet Adkins  * @param valuesDict Map of all sensor DBus values.
220*c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
221*c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
222*c9563608SJanet Adkins  * @return Health value for sensor.
223*c9563608SJanet Adkins  */
224*c9563608SJanet Adkins inline std::string getHealth(nlohmann::json& sensorJson,
225*c9563608SJanet Adkins                              const dbus::utility::DBusPropertiesMap& valuesDict,
226*c9563608SJanet Adkins                              const InventoryItem* inventoryItem)
227*c9563608SJanet Adkins {
228*c9563608SJanet Adkins     // Get current health value (if any) in the sensor JSON object.  Some JSON
229*c9563608SJanet Adkins     // objects contain multiple sensors (such as PowerSupplies).  We want to set
230*c9563608SJanet Adkins     // the overall health to be the most severe of any of the sensors.
231*c9563608SJanet Adkins     std::string currentHealth;
232*c9563608SJanet Adkins     auto statusIt = sensorJson.find("Status");
233*c9563608SJanet Adkins     if (statusIt != sensorJson.end())
234*c9563608SJanet Adkins     {
235*c9563608SJanet Adkins         auto healthIt = statusIt->find("Health");
236*c9563608SJanet Adkins         if (healthIt != statusIt->end())
237*c9563608SJanet Adkins         {
238*c9563608SJanet Adkins             std::string* health = healthIt->get_ptr<std::string*>();
239*c9563608SJanet Adkins             if (health != nullptr)
240*c9563608SJanet Adkins             {
241*c9563608SJanet Adkins                 currentHealth = *health;
242*c9563608SJanet Adkins             }
243*c9563608SJanet Adkins         }
244*c9563608SJanet Adkins     }
245*c9563608SJanet Adkins 
246*c9563608SJanet Adkins     // If current health in JSON object is already Critical, return that.  This
247*c9563608SJanet Adkins     // should override the sensor health, which might be less severe.
248*c9563608SJanet Adkins     if (currentHealth == "Critical")
249*c9563608SJanet Adkins     {
250*c9563608SJanet Adkins         return "Critical";
251*c9563608SJanet Adkins     }
252*c9563608SJanet Adkins 
253*c9563608SJanet Adkins     const bool* criticalAlarmHigh = nullptr;
254*c9563608SJanet Adkins     const bool* criticalAlarmLow = nullptr;
255*c9563608SJanet Adkins     const bool* warningAlarmHigh = nullptr;
256*c9563608SJanet Adkins     const bool* warningAlarmLow = nullptr;
257*c9563608SJanet Adkins 
258*c9563608SJanet Adkins     const bool success = sdbusplus::unpackPropertiesNoThrow(
259*c9563608SJanet Adkins         dbus_utils::UnpackErrorPrinter(), valuesDict, "CriticalAlarmHigh",
260*c9563608SJanet Adkins         criticalAlarmHigh, "CriticalAlarmLow", criticalAlarmLow,
261*c9563608SJanet Adkins         "WarningAlarmHigh", warningAlarmHigh, "WarningAlarmLow",
262*c9563608SJanet Adkins         warningAlarmLow);
263*c9563608SJanet Adkins 
264*c9563608SJanet Adkins     if (success)
265*c9563608SJanet Adkins     {
266*c9563608SJanet Adkins         // Check if sensor has critical threshold alarm
267*c9563608SJanet Adkins         if ((criticalAlarmHigh != nullptr && *criticalAlarmHigh) ||
268*c9563608SJanet Adkins             (criticalAlarmLow != nullptr && *criticalAlarmLow))
269*c9563608SJanet Adkins         {
270*c9563608SJanet Adkins             return "Critical";
271*c9563608SJanet Adkins         }
272*c9563608SJanet Adkins     }
273*c9563608SJanet Adkins 
274*c9563608SJanet Adkins     // Check if associated inventory item is not functional
275*c9563608SJanet Adkins     if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
276*c9563608SJanet Adkins     {
277*c9563608SJanet Adkins         return "Critical";
278*c9563608SJanet Adkins     }
279*c9563608SJanet Adkins 
280*c9563608SJanet Adkins     // If current health in JSON object is already Warning, return that. This
281*c9563608SJanet Adkins     // should override the sensor status, which might be less severe.
282*c9563608SJanet Adkins     if (currentHealth == "Warning")
283*c9563608SJanet Adkins     {
284*c9563608SJanet Adkins         return "Warning";
285*c9563608SJanet Adkins     }
286*c9563608SJanet Adkins 
287*c9563608SJanet Adkins     if (success)
288*c9563608SJanet Adkins     {
289*c9563608SJanet Adkins         // Check if sensor has warning threshold alarm
290*c9563608SJanet Adkins         if ((warningAlarmHigh != nullptr && *warningAlarmHigh) ||
291*c9563608SJanet Adkins             (warningAlarmLow != nullptr && *warningAlarmLow))
292*c9563608SJanet Adkins         {
293*c9563608SJanet Adkins             return "Warning";
294*c9563608SJanet Adkins         }
295*c9563608SJanet Adkins     }
296*c9563608SJanet Adkins 
297*c9563608SJanet Adkins     return "OK";
298*c9563608SJanet Adkins }
299*c9563608SJanet Adkins 
300*c9563608SJanet Adkins inline void setLedState(nlohmann::json& sensorJson,
301*c9563608SJanet Adkins                         const InventoryItem* inventoryItem)
302*c9563608SJanet Adkins {
303*c9563608SJanet Adkins     if (inventoryItem != nullptr && !inventoryItem->ledObjectPath.empty())
304*c9563608SJanet Adkins     {
305*c9563608SJanet Adkins         switch (inventoryItem->ledState)
306*c9563608SJanet Adkins         {
307*c9563608SJanet Adkins             case LedState::OFF:
308*c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Off;
309*c9563608SJanet Adkins                 break;
310*c9563608SJanet Adkins             case LedState::ON:
311*c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Lit;
312*c9563608SJanet Adkins                 break;
313*c9563608SJanet Adkins             case LedState::BLINK:
314*c9563608SJanet Adkins                 sensorJson["IndicatorLED"] = resource::IndicatorLED::Blinking;
315*c9563608SJanet Adkins                 break;
316*c9563608SJanet Adkins             default:
317*c9563608SJanet Adkins                 break;
318*c9563608SJanet Adkins         }
319*c9563608SJanet Adkins     }
320*c9563608SJanet Adkins }
321*c9563608SJanet Adkins 
322*c9563608SJanet Adkins /**
323*c9563608SJanet Adkins  * @brief Builds a json sensor representation of a sensor.
324*c9563608SJanet Adkins  * @param sensorName  The name of the sensor to be built
325*c9563608SJanet Adkins  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
326*c9563608SJanet Adkins  * build
327*c9563608SJanet Adkins  * @param chassisSubNode The subnode (thermal, sensor, etc) of the sensor
328*c9563608SJanet Adkins  * @param propertiesDict A dictionary of the properties to build the sensor
329*c9563608SJanet Adkins  * from.
330*c9563608SJanet Adkins  * @param sensorJson  The json object to fill
331*c9563608SJanet Adkins  * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
332*c9563608SJanet Adkins  * be nullptr if no associated inventory item was found.
333*c9563608SJanet Adkins  */
334*c9563608SJanet Adkins inline void objectPropertiesToJson(
335*c9563608SJanet Adkins     std::string_view sensorName, std::string_view sensorType,
336*c9563608SJanet Adkins     std::string_view chassisSubNode,
337*c9563608SJanet Adkins     const dbus::utility::DBusPropertiesMap& propertiesDict,
338*c9563608SJanet Adkins     nlohmann::json& sensorJson, InventoryItem* inventoryItem)
339*c9563608SJanet Adkins {
340*c9563608SJanet Adkins     if (chassisSubNode == sensorsNode)
341*c9563608SJanet Adkins     {
342*c9563608SJanet Adkins         std::string subNodeEscaped = getSensorId(sensorName, sensorType);
343*c9563608SJanet Adkins         // For sensors in SensorCollection we set Id instead of MemberId,
344*c9563608SJanet Adkins         // including power sensors.
345*c9563608SJanet Adkins         sensorJson["Id"] = std::move(subNodeEscaped);
346*c9563608SJanet Adkins 
347*c9563608SJanet Adkins         std::string sensorNameEs(sensorName);
348*c9563608SJanet Adkins         std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
349*c9563608SJanet Adkins         sensorJson["Name"] = std::move(sensorNameEs);
350*c9563608SJanet Adkins     }
351*c9563608SJanet Adkins     else if (sensorType != "power")
352*c9563608SJanet Adkins     {
353*c9563608SJanet Adkins         // Set MemberId and Name for non-power sensors.  For PowerSupplies and
354*c9563608SJanet Adkins         // PowerControl, those properties have more general values because
355*c9563608SJanet Adkins         // multiple sensors can be stored in the same JSON object.
356*c9563608SJanet Adkins         std::string sensorNameEs(sensorName);
357*c9563608SJanet Adkins         std::replace(sensorNameEs.begin(), sensorNameEs.end(), '_', ' ');
358*c9563608SJanet Adkins         sensorJson["Name"] = std::move(sensorNameEs);
359*c9563608SJanet Adkins     }
360*c9563608SJanet Adkins 
361*c9563608SJanet Adkins     const bool* checkAvailable = nullptr;
362*c9563608SJanet Adkins     bool available = true;
363*c9563608SJanet Adkins     const bool success = sdbusplus::unpackPropertiesNoThrow(
364*c9563608SJanet Adkins         dbus_utils::UnpackErrorPrinter(), propertiesDict, "Available",
365*c9563608SJanet Adkins         checkAvailable);
366*c9563608SJanet Adkins     if (!success)
367*c9563608SJanet Adkins     {
368*c9563608SJanet Adkins         messages::internalError();
369*c9563608SJanet Adkins     }
370*c9563608SJanet Adkins     if (checkAvailable != nullptr)
371*c9563608SJanet Adkins     {
372*c9563608SJanet Adkins         available = *checkAvailable;
373*c9563608SJanet Adkins     }
374*c9563608SJanet Adkins 
375*c9563608SJanet Adkins     sensorJson["Status"]["State"] = getState(inventoryItem, available);
376*c9563608SJanet Adkins     sensorJson["Status"]["Health"] =
377*c9563608SJanet Adkins         getHealth(sensorJson, propertiesDict, inventoryItem);
378*c9563608SJanet Adkins 
379*c9563608SJanet Adkins     // Parameter to set to override the type we get from dbus, and force it to
380*c9563608SJanet Adkins     // int, regardless of what is available.  This is used for schemas like fan,
381*c9563608SJanet Adkins     // that require integers, not floats.
382*c9563608SJanet Adkins     bool forceToInt = false;
383*c9563608SJanet Adkins 
384*c9563608SJanet Adkins     nlohmann::json::json_pointer unit("/Reading");
385*c9563608SJanet Adkins     if (chassisSubNode == sensorsNode)
386*c9563608SJanet Adkins     {
387*c9563608SJanet Adkins         sensorJson["@odata.type"] = "#Sensor.v1_2_0.Sensor";
388*c9563608SJanet Adkins 
389*c9563608SJanet Adkins         sensor::ReadingType readingType = sensors::toReadingType(sensorType);
390*c9563608SJanet Adkins         if (readingType == sensor::ReadingType::Invalid)
391*c9563608SJanet Adkins         {
392*c9563608SJanet Adkins             BMCWEB_LOG_ERROR("Redfish cannot map reading type for {}",
393*c9563608SJanet Adkins                              sensorType);
394*c9563608SJanet Adkins         }
395*c9563608SJanet Adkins         else
396*c9563608SJanet Adkins         {
397*c9563608SJanet Adkins             sensorJson["ReadingType"] = readingType;
398*c9563608SJanet Adkins         }
399*c9563608SJanet Adkins 
400*c9563608SJanet Adkins         std::string_view readingUnits = sensors::toReadingUnits(sensorType);
401*c9563608SJanet Adkins         if (readingUnits.empty())
402*c9563608SJanet Adkins         {
403*c9563608SJanet Adkins             BMCWEB_LOG_ERROR("Redfish cannot map reading unit for {}",
404*c9563608SJanet Adkins                              sensorType);
405*c9563608SJanet Adkins         }
406*c9563608SJanet Adkins         else
407*c9563608SJanet Adkins         {
408*c9563608SJanet Adkins             sensorJson["ReadingUnits"] = readingUnits;
409*c9563608SJanet Adkins         }
410*c9563608SJanet Adkins     }
411*c9563608SJanet Adkins     else if (sensorType == "temperature")
412*c9563608SJanet Adkins     {
413*c9563608SJanet Adkins         unit = "/ReadingCelsius"_json_pointer;
414*c9563608SJanet Adkins         sensorJson["@odata.type"] = "#Thermal.v1_3_0.Temperature";
415*c9563608SJanet Adkins         // TODO(ed) Documentation says that path should be type fan_tach,
416*c9563608SJanet Adkins         // implementation seems to implement fan
417*c9563608SJanet Adkins     }
418*c9563608SJanet Adkins     else if (sensorType == "fan" || sensorType == "fan_tach")
419*c9563608SJanet Adkins     {
420*c9563608SJanet Adkins         unit = "/Reading"_json_pointer;
421*c9563608SJanet Adkins         sensorJson["ReadingUnits"] = thermal::ReadingUnits::RPM;
422*c9563608SJanet Adkins         sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
423*c9563608SJanet Adkins         setLedState(sensorJson, inventoryItem);
424*c9563608SJanet Adkins         forceToInt = true;
425*c9563608SJanet Adkins     }
426*c9563608SJanet Adkins     else if (sensorType == "fan_pwm")
427*c9563608SJanet Adkins     {
428*c9563608SJanet Adkins         unit = "/Reading"_json_pointer;
429*c9563608SJanet Adkins         sensorJson["ReadingUnits"] = thermal::ReadingUnits::Percent;
430*c9563608SJanet Adkins         sensorJson["@odata.type"] = "#Thermal.v1_3_0.Fan";
431*c9563608SJanet Adkins         setLedState(sensorJson, inventoryItem);
432*c9563608SJanet Adkins         forceToInt = true;
433*c9563608SJanet Adkins     }
434*c9563608SJanet Adkins     else if (sensorType == "voltage")
435*c9563608SJanet Adkins     {
436*c9563608SJanet Adkins         unit = "/ReadingVolts"_json_pointer;
437*c9563608SJanet Adkins         sensorJson["@odata.type"] = "#Power.v1_0_0.Voltage";
438*c9563608SJanet Adkins     }
439*c9563608SJanet Adkins     else if (sensorType == "power")
440*c9563608SJanet Adkins     {
441*c9563608SJanet Adkins         std::string lower;
442*c9563608SJanet Adkins         std::ranges::transform(sensorName, std::back_inserter(lower),
443*c9563608SJanet Adkins                                bmcweb::asciiToLower);
444*c9563608SJanet Adkins         if (lower == "total_power")
445*c9563608SJanet Adkins         {
446*c9563608SJanet Adkins             sensorJson["@odata.type"] = "#Power.v1_0_0.PowerControl";
447*c9563608SJanet Adkins             // Put multiple "sensors" into a single PowerControl, so have
448*c9563608SJanet Adkins             // generic names for MemberId and Name. Follows Redfish mockup.
449*c9563608SJanet Adkins             sensorJson["MemberId"] = "0";
450*c9563608SJanet Adkins             sensorJson["Name"] = "Chassis Power Control";
451*c9563608SJanet Adkins             unit = "/PowerConsumedWatts"_json_pointer;
452*c9563608SJanet Adkins         }
453*c9563608SJanet Adkins         else if (lower.find("input") != std::string::npos)
454*c9563608SJanet Adkins         {
455*c9563608SJanet Adkins             unit = "/PowerInputWatts"_json_pointer;
456*c9563608SJanet Adkins         }
457*c9563608SJanet Adkins         else
458*c9563608SJanet Adkins         {
459*c9563608SJanet Adkins             unit = "/PowerOutputWatts"_json_pointer;
460*c9563608SJanet Adkins         }
461*c9563608SJanet Adkins     }
462*c9563608SJanet Adkins     else
463*c9563608SJanet Adkins     {
464*c9563608SJanet Adkins         BMCWEB_LOG_ERROR("Redfish cannot map object type for {}", sensorName);
465*c9563608SJanet Adkins         return;
466*c9563608SJanet Adkins     }
467*c9563608SJanet Adkins     // Map of dbus interface name, dbus property name and redfish property_name
468*c9563608SJanet Adkins     std::vector<
469*c9563608SJanet Adkins         std::tuple<const char*, const char*, nlohmann::json::json_pointer>>
470*c9563608SJanet Adkins         properties;
471*c9563608SJanet Adkins     properties.reserve(7);
472*c9563608SJanet Adkins 
473*c9563608SJanet Adkins     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
474*c9563608SJanet Adkins 
475*c9563608SJanet Adkins     if (chassisSubNode == sensorsNode)
476*c9563608SJanet Adkins     {
477*c9563608SJanet Adkins         properties.emplace_back(
478*c9563608SJanet Adkins             "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningHigh",
479*c9563608SJanet Adkins             "/Thresholds/UpperCaution/Reading"_json_pointer);
480*c9563608SJanet Adkins         properties.emplace_back(
481*c9563608SJanet Adkins             "xyz.openbmc_project.Sensor.Threshold.Warning", "WarningLow",
482*c9563608SJanet Adkins             "/Thresholds/LowerCaution/Reading"_json_pointer);
483*c9563608SJanet Adkins         properties.emplace_back(
484*c9563608SJanet Adkins             "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalHigh",
485*c9563608SJanet Adkins             "/Thresholds/UpperCritical/Reading"_json_pointer);
486*c9563608SJanet Adkins         properties.emplace_back(
487*c9563608SJanet Adkins             "xyz.openbmc_project.Sensor.Threshold.Critical", "CriticalLow",
488*c9563608SJanet Adkins             "/Thresholds/LowerCritical/Reading"_json_pointer);
489*c9563608SJanet Adkins     }
490*c9563608SJanet Adkins     else if (sensorType != "power")
491*c9563608SJanet Adkins     {
492*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
493*c9563608SJanet Adkins                                 "WarningHigh",
494*c9563608SJanet Adkins                                 "/UpperThresholdNonCritical"_json_pointer);
495*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
496*c9563608SJanet Adkins                                 "WarningLow",
497*c9563608SJanet Adkins                                 "/LowerThresholdNonCritical"_json_pointer);
498*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
499*c9563608SJanet Adkins                                 "CriticalHigh",
500*c9563608SJanet Adkins                                 "/UpperThresholdCritical"_json_pointer);
501*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
502*c9563608SJanet Adkins                                 "CriticalLow",
503*c9563608SJanet Adkins                                 "/LowerThresholdCritical"_json_pointer);
504*c9563608SJanet Adkins     }
505*c9563608SJanet Adkins 
506*c9563608SJanet Adkins     // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
507*c9563608SJanet Adkins 
508*c9563608SJanet Adkins     if (chassisSubNode == sensorsNode)
509*c9563608SJanet Adkins     {
510*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
511*c9563608SJanet Adkins                                 "/ReadingRangeMin"_json_pointer);
512*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
513*c9563608SJanet Adkins                                 "/ReadingRangeMax"_json_pointer);
514*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Accuracy",
515*c9563608SJanet Adkins                                 "Accuracy", "/Accuracy"_json_pointer);
516*c9563608SJanet Adkins     }
517*c9563608SJanet Adkins     else if (sensorType == "temperature")
518*c9563608SJanet Adkins     {
519*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
520*c9563608SJanet Adkins                                 "/MinReadingRangeTemp"_json_pointer);
521*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
522*c9563608SJanet Adkins                                 "/MaxReadingRangeTemp"_json_pointer);
523*c9563608SJanet Adkins     }
524*c9563608SJanet Adkins     else if (sensorType != "power")
525*c9563608SJanet Adkins     {
526*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
527*c9563608SJanet Adkins                                 "/MinReadingRange"_json_pointer);
528*c9563608SJanet Adkins         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
529*c9563608SJanet Adkins                                 "/MaxReadingRange"_json_pointer);
530*c9563608SJanet Adkins     }
531*c9563608SJanet Adkins 
532*c9563608SJanet Adkins     for (const std::tuple<const char*, const char*,
533*c9563608SJanet Adkins                           nlohmann::json::json_pointer>& p : properties)
534*c9563608SJanet Adkins     {
535*c9563608SJanet Adkins         for (const auto& [valueName, valueVariant] : propertiesDict)
536*c9563608SJanet Adkins         {
537*c9563608SJanet Adkins             if (valueName != std::get<1>(p))
538*c9563608SJanet Adkins             {
539*c9563608SJanet Adkins                 continue;
540*c9563608SJanet Adkins             }
541*c9563608SJanet Adkins 
542*c9563608SJanet Adkins             // The property we want to set may be nested json, so use
543*c9563608SJanet Adkins             // a json_pointer for easy indexing into the json structure.
544*c9563608SJanet Adkins             const nlohmann::json::json_pointer& key = std::get<2>(p);
545*c9563608SJanet Adkins 
546*c9563608SJanet Adkins             const double* doubleValue = std::get_if<double>(&valueVariant);
547*c9563608SJanet Adkins             if (doubleValue == nullptr)
548*c9563608SJanet Adkins             {
549*c9563608SJanet Adkins                 BMCWEB_LOG_ERROR("Got value interface that wasn't double");
550*c9563608SJanet Adkins                 continue;
551*c9563608SJanet Adkins             }
552*c9563608SJanet Adkins             if (!std::isfinite(*doubleValue))
553*c9563608SJanet Adkins             {
554*c9563608SJanet Adkins                 if (valueName == "Value")
555*c9563608SJanet Adkins                 {
556*c9563608SJanet Adkins                     // Readings are allowed to be NAN for unavailable;  coerce
557*c9563608SJanet Adkins                     // them to null in the json response.
558*c9563608SJanet Adkins                     sensorJson[key] = nullptr;
559*c9563608SJanet Adkins                     continue;
560*c9563608SJanet Adkins                 }
561*c9563608SJanet Adkins                 BMCWEB_LOG_WARNING("Sensor value for {} was unexpectedly {}",
562*c9563608SJanet Adkins                                    valueName, *doubleValue);
563*c9563608SJanet Adkins                 continue;
564*c9563608SJanet Adkins             }
565*c9563608SJanet Adkins             if (forceToInt)
566*c9563608SJanet Adkins             {
567*c9563608SJanet Adkins                 sensorJson[key] = static_cast<int64_t>(*doubleValue);
568*c9563608SJanet Adkins             }
569*c9563608SJanet Adkins             else
570*c9563608SJanet Adkins             {
571*c9563608SJanet Adkins                 sensorJson[key] = *doubleValue;
572*c9563608SJanet Adkins             }
573*c9563608SJanet Adkins         }
574*c9563608SJanet Adkins     }
575*c9563608SJanet Adkins }
576*c9563608SJanet Adkins 
5771516c21bSJanet Adkins } // namespace sensor_utils
5781516c21bSJanet Adkins } // namespace redfish
579