Redfish: Move power supplies to Power schema

Made the following enhancements related to power supplies in Redfish:
* Moved power supplies from Chassis schema to Power schema
* Added support for multiple power supplies within one chassis
* Added support for multiple sensors associated with one power supply
* Set power supply Name and MemberId to inventory item name (like
  powersupply0) rather than sensor name (like ps0_input_power).
* Set power supply VPD fields like Manufacturer and SerialNumber

Also improved support for power sensors that are not related to power
supplies, such as VRM sensors.  These sensors are no longer treated as
power supplies, and they will not be included in the Power schema
output.  They should instead be included in the SensorCollection output
when that support is completed.

Uses an ObjectMapper association named "inventory" to find the power
supply inventory item associated with a power sensor.  For example:

  /xyz/openbmc_project/sensors/power/ps0_input_power/inventory ->
  /xyz/openbmc_project/inventory/system/chassis/motherboard/powersupply0

See https://github.com/openbmc/docs/blob/master/sensor-architecture.md
for more information on associations between sensors and low-level
hardware items.

Test Plan:
  https://gist.github.com/smccarney/43b2fae304575302af61332a71280d74

Tested: Verified Power and Thermal output on a Witherspoon system.
        Tested with power sensors that were/were not associated
        with power supplies.  Verified power supply properties in
        Power output.  Verified that power supplies are no longer
        returned in Chassis collection output.  Ran Redfish
        Service Validator.

Change-Id: I50db389b5df011bfb561d31aafb33cc4bf7fcde6
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/redfish-core/lib/chassis.hpp b/redfish-core/lib/chassis.hpp
index 4426402..8553b28 100644
--- a/redfish-core/lib/chassis.hpp
+++ b/redfish-core/lib/chassis.hpp
@@ -182,10 +182,9 @@
             "/redfish/v1/$metadata#ChassisCollection.ChassisCollection";
         res.jsonValue["Name"] = "Chassis Collection";
 
-        const std::array<const char *, 3> interfaces = {
+        const std::array<const char *, 2> interfaces = {
             "xyz.openbmc_project.Inventory.Item.Board",
-            "xyz.openbmc_project.Inventory.Item.Chassis",
-            "xyz.openbmc_project.Inventory.Item.PowerSupply"};
+            "xyz.openbmc_project.Inventory.Item.Chassis"};
 
         auto asyncResp = std::make_shared<AsyncResp>(res);
         crow::connections::systemBus->async_method_call(
@@ -247,10 +246,9 @@
     void doGet(crow::Response &res, const crow::Request &req,
                const std::vector<std::string> &params) override
     {
-        const std::array<const char *, 3> interfaces = {
+        const std::array<const char *, 2> interfaces = {
             "xyz.openbmc_project.Inventory.Item.Board",
-            "xyz.openbmc_project.Inventory.Item.Chassis",
-            "xyz.openbmc_project.Inventory.Item.PowerSupply"};
+            "xyz.openbmc_project.Inventory.Item.Chassis"};
 
         // Check if there is required param, truly entering this shall be
         // impossible.
diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp
index 0ed47f7..3fd86d6 100644
--- a/redfish-core/lib/sensors.hpp
+++ b/redfish-core/lib/sensors.hpp
@@ -32,7 +32,8 @@
     std::pair<std::string,
               std::vector<std::pair<std::string, std::vector<std::string>>>>>;
 
-using SensorVariant = std::variant<int64_t, double, uint32_t, bool>;
+using SensorVariant =
+    std::variant<int64_t, double, uint32_t, bool, std::string>;
 
 using ManagedObjectsVectorType = std::vector<std::pair<
     sdbusplus::message::object_path,
@@ -73,6 +74,37 @@
 };
 
 /**
+ * D-Bus inventory item associated with one or more sensors.
+ */
+class InventoryItem
+{
+  public:
+    InventoryItem(const std::string& objPath) :
+        objectPath(objPath), name(), isPresent(true), isFunctional(true),
+        isPowerSupply(false), manufacturer(), model(), partNumber(),
+        serialNumber(), sensors()
+    {
+        // Set inventory item name to last node of object path
+        auto pos = objectPath.rfind('/');
+        if ((pos != std::string::npos) && ((pos + 1) < objectPath.size()))
+        {
+            name = objectPath.substr(pos + 1);
+        }
+    }
+
+    std::string objectPath;
+    std::string name;
+    bool isPresent;
+    bool isFunctional;
+    bool isPowerSupply;
+    std::string manufacturer;
+    std::string model;
+    std::string partNumber;
+    std::string serialNumber;
+    std::set<std::string> sensors;
+};
+
+/**
  * @brief Get objects with connection necessary for sensors
  * @param SensorsAsyncResp Pointer to object holding response data
  * @param sensorNames Sensors retrieved from chassis
@@ -224,10 +256,9 @@
                 Callback&& callback)
 {
     BMCWEB_LOG_DEBUG << "getChassis enter";
-    const std::array<const char*, 3> interfaces = {
+    const std::array<const char*, 2> interfaces = {
         "xyz.openbmc_project.Inventory.Item.Board",
-        "xyz.openbmc_project.Inventory.Item.Chassis",
-        "xyz.openbmc_project.Inventory.Item.PowerSupply"};
+        "xyz.openbmc_project.Inventory.Item.Chassis"};
     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp](
                            const boost::system::error_code ec,
                            const std::vector<std::string>& chassisPaths) {
@@ -406,15 +437,61 @@
 }
 
 /**
- * @brief Retrieves the health from a sensor .
- * @param interfacesDict   Map of all sensor interfaces
+ * @brief Returns the Redfish State value for the specified inventory item.
+ * @param inventoryItem D-Bus inventory item associated with a sensor.
+ * @return State value for inventory item.
  */
+static std::string getState(const InventoryItem* inventoryItem)
+{
+    if ((inventoryItem != nullptr) && !(inventoryItem->isPresent))
+    {
+        return "Absent";
+    }
 
+    return "Enabled";
+}
+
+/**
+ * @brief Returns the Redfish Health value for the specified sensor.
+ * @param sensorJson Sensor JSON object.
+ * @param interfacesDict Map of all sensor interfaces.
+ * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
+ * be nullptr if no associated inventory item was found.
+ * @return Health value for sensor.
+ */
 static std::string getHealth(
+    nlohmann::json& sensorJson,
     const boost::container::flat_map<
         std::string, boost::container::flat_map<std::string, SensorVariant>>&
-        interfacesDict)
+        interfacesDict,
+    const InventoryItem* inventoryItem)
 {
+    // Get current health value (if any) in the sensor JSON object.  Some JSON
+    // objects contain multiple sensors (such as PowerSupplies).  We want to set
+    // the overall health to be the most severe of any of the sensors.
+    std::string currentHealth;
+    auto statusIt = sensorJson.find("Status");
+    if (statusIt != sensorJson.end())
+    {
+        auto healthIt = statusIt->find("Health");
+        if (healthIt != statusIt->end())
+        {
+            std::string* health = healthIt->get_ptr<std::string*>();
+            if (health != nullptr)
+            {
+                currentHealth = *health;
+            }
+        }
+    }
+
+    // If current health in JSON object is already Critical, return that.  This
+    // should override the sensor health, which might be less severe.
+    if (currentHealth == "Critical")
+    {
+        return "Critical";
+    }
+
+    // Check if sensor has critical threshold alarm
     auto criticalThresholdIt =
         interfacesDict.find("xyz.openbmc_project.Sensor.Threshold.Critical");
     if (criticalThresholdIt != interfacesDict.end())
@@ -449,6 +526,20 @@
         }
     }
 
+    // Check if associated inventory item is not functional
+    if ((inventoryItem != nullptr) && !(inventoryItem->isFunctional))
+    {
+        return "Critical";
+    }
+
+    // If current health in JSON object is already Warning, return that.  This
+    // should override the sensor status, which might be less severe.
+    if (currentHealth == "Warning")
+    {
+        return "Warning";
+    }
+
+    // Check if sensor has warning threshold alarm
     auto warningThresholdIt =
         interfacesDict.find("xyz.openbmc_project.Sensor.Threshold.Warning");
     if (warningThresholdIt != interfacesDict.end())
@@ -482,6 +573,7 @@
             }
         }
     }
+
     return "OK";
 }
 
@@ -493,13 +585,15 @@
  * @param interfacesDict  A dictionary of the interfaces and properties of said
  * interfaces to be built from
  * @param sensor_json  The json object to fill
+ * @param inventoryItem D-Bus inventory item associated with the sensor.  Will
+ * be nullptr if no associated inventory item was found.
  */
 void objectInterfacesToJson(
     const std::string& sensorName, const std::string& sensorType,
     const boost::container::flat_map<
         std::string, boost::container::flat_map<std::string, SensorVariant>>&
         interfacesDict,
-    nlohmann::json& sensor_json)
+    nlohmann::json& sensor_json, InventoryItem* inventoryItem)
 {
     // We need a value interface before we can do anything with it
     auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
@@ -523,11 +617,18 @@
         }
     }
 
-    sensor_json["MemberId"] = sensorName;
-    sensor_json["Name"] = boost::replace_all_copy(sensorName, "_", " ");
+    // Set MemberId and Name for non-power sensors.  For PowerSupplies and
+    // PowerControl, those properties have more general values because multiple
+    // sensors can be stored in the same JSON object.
+    if (sensorType != "power")
+    {
+        sensor_json["MemberId"] = sensorName;
+        sensor_json["Name"] = boost::replace_all_copy(sensorName, "_", " ");
+    }
 
-    sensor_json["Status"]["State"] = "Enabled";
-    sensor_json["Status"]["Health"] = getHealth(interfacesDict);
+    sensor_json["Status"]["State"] = getState(inventoryItem);
+    sensor_json["Status"]["Health"] =
+        getHealth(sensor_json, interfacesDict, inventoryItem);
 
     // Parameter to set to override the type we get from dbus, and force it to
     // int, regardless of what is available.  This is used for schemas like fan,
@@ -595,8 +696,7 @@
 
     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
 
-    // If sensor type doesn't map to Redfish PowerSupply, add threshold props
-    if ((sensorType != "current") && (sensorType != "power"))
+    if (sensorType != "power")
     {
         properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
                                 "WarningHigh", "UpperThresholdNonCritical");
@@ -617,9 +717,8 @@
         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
                                 "MaxReadingRangeTemp");
     }
-    else if ((sensorType != "current") && (sensorType != "power"))
+    else if (sensorType != "power")
     {
-        // Sensor type doesn't map to Redfish PowerSupply; add min/max props
         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
                                 "MinReadingRange");
         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
@@ -918,162 +1017,256 @@
 }
 
 /**
- * @brief Finds the JSON object for the specified sensor.
- *
- * Searches the JSON response in sensorsAsyncResp for an object corresponding to
- * the specified sensor.
- *
- * @param sensorsAsyncResp Pointer to object holding response data.
- * @param sensorName DBus object path to the sensor.
- * @return Pointer to JSON object, or nullptr if object not found.
+ * @brief Finds the inventory item with the specified object path.
+ * @param inventoryItems D-Bus inventory items associated with sensors.
+ * @param invItemObjPath D-Bus object path of inventory item.
+ * @return Inventory item within vector, or nullptr if no match found.
  */
-static nlohmann::json*
-    findSensorJson(std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
-                   const std::string& sensorName)
+static InventoryItem* findInventoryItem(
+    std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
+    const std::string& invItemObjPath)
 {
-    // Get base name of sensor
-    std::size_t lastSlash = sensorName.rfind('/');
-    if (lastSlash != std::string::npos)
+    for (InventoryItem& inventoryItem : *inventoryItems)
     {
-        std::string baseSensorName = sensorName.substr(lastSlash + 1);
-
-        // Loop through JSON sensor groups that could contain sensor
-        nlohmann::json& response = sensorsAsyncResp->res.jsonValue;
-        std::array<std::string, 4> sensorGroups{"Temperatures", "Fans",
-                                                "Voltages", "PowerSupplies"};
-        for (const std::string& sensorGroup : sensorGroups)
+        if (inventoryItem.objectPath == invItemObjPath)
         {
-            nlohmann::json::iterator groupIt = response.find(sensorGroup);
-            if (groupIt != response.end())
-            {
-                // Loop through sensors in current group
-                for (nlohmann::json& sensorJson : *groupIt)
-                {
-                    // Check if this is the sensor we are looking for
-                    nlohmann::json::iterator memberIdIt =
-                        sensorJson.find("MemberId");
-                    if (memberIdIt != sensorJson.end())
-                    {
-                        std::string* memberId =
-                            memberIdIt->get_ptr<std::string*>();
-                        if ((memberId != nullptr) &&
-                            (*memberId == baseSensorName))
-                        {
-                            return &sensorJson;
-                        }
-                    }
-                }
-            }
+            return &inventoryItem;
         }
     }
-
-    // Unable to find JSON object for specified sensor
     return nullptr;
 }
 
 /**
- * @brief Updates sensor status in JSON response based on inventory item status.
- *
- * Updates the status of the specified sensor based on the status of a related
- * inventory item.
- *
- * Modifies the Redfish Status property in the JSON response if the inventory
- * item indicates the hardware is not present or not functional.
- *
- * The D-Bus Present and Functional properties are typically on the inventory
- * item rather than the sensor.
- *
- * @param sensorsAsyncResp Pointer to object holding response data.
- * @param sensorName DBus object path to the sensor.
- * @param interfacesDict Map containing the interfaces and properties of the
- * inventory item associated with this sensor.
+ * @brief Finds the inventory item associated with the specified sensor.
+ * @param inventoryItems D-Bus inventory items associated with sensors.
+ * @param sensorObjPath D-Bus object path of sensor.
+ * @return Inventory item within vector, or nullptr if no match found.
  */
-static void updateSensorStatus(
-    std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
-    const std::string& sensorName,
+static InventoryItem* findInventoryItemForSensor(
+    std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
+    const std::string& sensorObjPath)
+{
+    for (InventoryItem& inventoryItem : *inventoryItems)
+    {
+        if (inventoryItem.sensors.count(sensorObjPath) > 0)
+        {
+            return &inventoryItem;
+        }
+    }
+    return nullptr;
+}
+
+/**
+ * @brief Adds inventory item and associated sensor to specified vector.
+ *
+ * Adds a new InventoryItem to the vector if necessary.  Searches for an
+ * existing InventoryItem with the specified object path.  If not found, one is
+ * added to the vector.
+ *
+ * Next, the specified sensor is added to the set of sensors associated with the
+ * InventoryItem.
+ *
+ * @param inventoryItems D-Bus inventory items associated with sensors.
+ * @param invItemObjPath D-Bus object path of inventory item.
+ * @param sensorObjPath D-Bus object path of sensor
+ */
+static void
+    addInventoryItem(std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
+                     const std::string& invItemObjPath,
+                     const std::string& sensorObjPath)
+{
+    // Look for inventory item in vector
+    InventoryItem* inventoryItem =
+        findInventoryItem(inventoryItems, invItemObjPath);
+
+    // If inventory item doesn't exist in vector, add it
+    if (inventoryItem == nullptr)
+    {
+        inventoryItems->emplace_back(invItemObjPath);
+        inventoryItem = &(inventoryItems->back());
+    }
+
+    // Add sensor to set of sensors associated with inventory item
+    inventoryItem->sensors.emplace(sensorObjPath);
+}
+
+/**
+ * @brief Stores D-Bus data in the specified inventory item.
+ *
+ * Finds D-Bus data in the specified map of interfaces.  Stores the data in the
+ * specified InventoryItem.
+ *
+ * This data is later used to provide sensor property values in the JSON
+ * response.
+ *
+ * @param inventoryItem Inventory item where data will be stored.
+ * @param interfacesDict Map containing D-Bus interfaces and their properties
+ * for the specified inventory item.
+ */
+static void storeInventoryItemData(
+    InventoryItem& inventoryItem,
     const boost::container::flat_map<
         std::string, boost::container::flat_map<std::string, SensorVariant>>&
         interfacesDict)
 {
-    // Find the JSON object in the response for this sensor
-    nlohmann::json* sensorJson = findSensorJson(sensorsAsyncResp, sensorName);
-    if (sensorJson != nullptr)
+    // Get properties from Inventory.Item interface
+    auto interfaceIt =
+        interfacesDict.find("xyz.openbmc_project.Inventory.Item");
+    if (interfaceIt != interfacesDict.end())
     {
-        // Get Inventory.Item.Present property of inventory item
-        auto itemIt = interfacesDict.find("xyz.openbmc_project.Inventory.Item");
-        if (itemIt != interfacesDict.end())
+        auto propertyIt = interfaceIt->second.find("Present");
+        if (propertyIt != interfaceIt->second.end())
         {
-            auto presentIt = itemIt->second.find("Present");
-            if (presentIt != itemIt->second.end())
+            const bool* value = std::get_if<bool>(&propertyIt->second);
+            if (value != nullptr)
             {
-                const bool* present = std::get_if<bool>(&presentIt->second);
-                if ((present != nullptr) && (*present == false))
-                {
-                    // Inventory item is not present; update sensor State
-                    (*sensorJson)["Status"]["State"] = "Absent";
-                }
+                inventoryItem.isPresent = *value;
+            }
+        }
+    }
+
+    // Check if Inventory.Item.PowerSupply interface is present
+    interfaceIt =
+        interfacesDict.find("xyz.openbmc_project.Inventory.Item.PowerSupply");
+    if (interfaceIt != interfacesDict.end())
+    {
+        inventoryItem.isPowerSupply = true;
+    }
+
+    // Get properties from Inventory.Decorator.Asset interface
+    interfaceIt =
+        interfacesDict.find("xyz.openbmc_project.Inventory.Decorator.Asset");
+    if (interfaceIt != interfacesDict.end())
+    {
+        auto propertyIt = interfaceIt->second.find("Manufacturer");
+        if (propertyIt != interfaceIt->second.end())
+        {
+            const std::string* value =
+                std::get_if<std::string>(&propertyIt->second);
+            if (value != nullptr)
+            {
+                inventoryItem.manufacturer = *value;
             }
         }
 
-        // Get OperationalStatus.Functional property of inventory item
-        auto opStatusIt = interfacesDict.find(
-            "xyz.openbmc_project.State.Decorator.OperationalStatus");
-        if (opStatusIt != interfacesDict.end())
+        propertyIt = interfaceIt->second.find("Model");
+        if (propertyIt != interfaceIt->second.end())
         {
-            auto functionalIt = opStatusIt->second.find("Functional");
-            if (functionalIt != opStatusIt->second.end())
+            const std::string* value =
+                std::get_if<std::string>(&propertyIt->second);
+            if (value != nullptr)
             {
-                const bool* functional =
-                    std::get_if<bool>(&functionalIt->second);
-                if ((functional != nullptr) && (*functional == false))
-                {
-                    // Inventory item is not functional; update sensor Health
-                    (*sensorJson)["Status"]["Health"] = "Critical";
-                }
+                inventoryItem.model = *value;
+            }
+        }
+
+        propertyIt = interfaceIt->second.find("PartNumber");
+        if (propertyIt != interfaceIt->second.end())
+        {
+            const std::string* value =
+                std::get_if<std::string>(&propertyIt->second);
+            if (value != nullptr)
+            {
+                inventoryItem.partNumber = *value;
+            }
+        }
+
+        propertyIt = interfaceIt->second.find("SerialNumber");
+        if (propertyIt != interfaceIt->second.end())
+        {
+            const std::string* value =
+                std::get_if<std::string>(&propertyIt->second);
+            if (value != nullptr)
+            {
+                inventoryItem.serialNumber = *value;
+            }
+        }
+    }
+
+    // Get properties from State.Decorator.OperationalStatus interface
+    interfaceIt = interfacesDict.find(
+        "xyz.openbmc_project.State.Decorator.OperationalStatus");
+    if (interfaceIt != interfacesDict.end())
+    {
+        auto propertyIt = interfaceIt->second.find("Functional");
+        if (propertyIt != interfaceIt->second.end())
+        {
+            const bool* value = std::get_if<bool>(&propertyIt->second);
+            if (value != nullptr)
+            {
+                inventoryItem.isFunctional = *value;
             }
         }
     }
 }
 
 /**
- * @brief Gets status of inventory items associated with sensors.
+ * @brief Gets D-Bus data for inventory items associated with sensors.
  *
- * Gets the D-Bus status properties for the inventory items associated with
- * sensors.
+ * Uses the specified connections (services) to obtain D-Bus data for inventory
+ * items associated with sensors.  Stores the resulting data in the
+ * inventoryItems vector.
  *
- * Updates the Redfish sensors status in the JSON response, if needed, based on
- * the inventory items status.
+ * This data is later used to provide sensor property values in the JSON
+ * response.
+ *
+ * Finds the inventory item data asynchronously.  Invokes callback when data has
+ * been obtained.
+ *
+ * The callback must have the following signature:
+ *   @code
+ *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
+ *   @endcode
+ *
+ * This function is called recursively, obtaining data asynchronously from one
+ * connection in each call.  This ensures the callback is not invoked until the
+ * last asynchronous function has completed.
  *
  * @param sensorsAsyncResp Pointer to object holding response data.
- * @param sensorToInvMap Mappings from sensor object path to the associated
- * inventory object path.
- * @param invConnections Connections that provide the status
- * interfaces/properties for the inventory items.
+ * @param inventoryItems D-Bus inventory items associated with sensors.
+ * @param invConnections Connections that provide data for the inventory items.
  * @param objectMgrPaths Mappings from connection name to DBus object path that
  * implements ObjectManager.
+ * @param callback Callback to invoke when inventory data has been obtained.
+ * @param invConnectionsIndex Current index in invConnections.  Only specified
+ * in recursive calls to this function.
  */
-static void getInventoryItemsStatus(
+template <typename Callback>
+static void getInventoryItemsData(
     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
-    std::shared_ptr<boost::container::flat_map<std::string, std::string>>
-        sensorToInvMap,
+    std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
     std::shared_ptr<boost::container::flat_set<std::string>> invConnections,
     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
-        objectMgrPaths)
+        objectMgrPaths,
+    Callback&& callback, int invConnectionsIndex = 0)
 {
-    BMCWEB_LOG_DEBUG << "getInventoryItemsStatus enter";
+    BMCWEB_LOG_DEBUG << "getInventoryItemsData enter";
 
-    // Loop through all connections providing inventory item status
-    for (const std::string& invConnection : *invConnections)
+    // If no more connections left, call callback
+    if (invConnectionsIndex >= invConnections->size())
     {
+        callback(inventoryItems);
+        BMCWEB_LOG_DEBUG << "getInventoryItemsData exit";
+        return;
+    }
+
+    // Get inventory item data from current connection
+    auto it = invConnections->nth(invConnectionsIndex);
+    if (it != invConnections->end())
+    {
+        const std::string& invConnection = *it;
+
         // Response handler for GetManagedObjects
-        auto respHandler = [sensorsAsyncResp,
-                            sensorToInvMap](const boost::system::error_code ec,
-                                            ManagedObjectsVectorType& resp) {
-            BMCWEB_LOG_DEBUG << "getInventoryItemsStatus respHandler enter";
+        auto respHandler = [sensorsAsyncResp, inventoryItems, invConnections,
+                            objectMgrPaths, callback{std::move(callback)},
+                            invConnectionsIndex](
+                               const boost::system::error_code ec,
+                               ManagedObjectsVectorType& resp) {
+            BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler enter";
             if (ec)
             {
                 BMCWEB_LOG_ERROR
-                    << "getInventoryItemsStatus respHandler DBus error " << ec;
+                    << "getInventoryItemsData respHandler DBus error " << ec;
                 messages::internalError(sensorsAsyncResp->res);
                 return;
             }
@@ -1084,20 +1277,22 @@
                 const std::string& objPath =
                     static_cast<const std::string&>(objDictEntry.first);
 
-                // Find all sensors associated with this inventory item
-                for (const std::pair<std::string, std::string>& pair :
-                     *sensorToInvMap)
+                // If this object path is one of the specified inventory items
+                InventoryItem* inventoryItem =
+                    findInventoryItem(inventoryItems, objPath);
+                if (inventoryItem != nullptr)
                 {
-                    if (pair.second == objPath)
-                    {
-                        // Update sensor status based on inventory item status
-                        updateSensorStatus(sensorsAsyncResp, pair.first,
-                                           objDictEntry.second);
-                    }
+                    // Store inventory data in InventoryItem
+                    storeInventoryItemData(*inventoryItem, objDictEntry.second);
                 }
             }
 
-            BMCWEB_LOG_DEBUG << "getInventoryItemsStatus respHandler exit";
+            // Recurse to get inventory item data from next connection
+            getInventoryItemsData(sensorsAsyncResp, inventoryItems,
+                                  invConnections, objectMgrPaths,
+                                  std::move(callback), invConnectionsIndex + 1);
+
+            BMCWEB_LOG_DEBUG << "getInventoryItemsData respHandler exit";
         };
 
         // Find DBus object path that implements ObjectManager for the current
@@ -1114,14 +1309,14 @@
             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
     }
 
-    BMCWEB_LOG_DEBUG << "getInventoryItemsStatus exit";
+    BMCWEB_LOG_DEBUG << "getInventoryItemsData exit";
 }
 
 /**
- * @brief Gets connections that provide status information on inventory items.
+ * @brief Gets connections that provide D-Bus data for inventory items.
  *
- * Gets the D-Bus connections (services) that provide the interfaces and
- * properties containing status information for the inventory items.
+ * Gets the D-Bus connections (services) that provide data for the inventory
+ * items that are associated with sensors.
  *
  * Finds the connections asynchronously.  Invokes callback when information has
  * been obtained.
@@ -1133,27 +1328,27 @@
  *   @endcode
  *
  * @param sensorsAsyncResp Pointer to object holding response data.
- * @param sensorToInvMap Mappings from sensor object path to the associated
- * inventory object path.
+ * @param inventoryItems D-Bus inventory items associated with sensors.
  * @param callback Callback to invoke when connections have been obtained.
  */
 template <typename Callback>
 static void getInventoryItemsConnections(
     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
-    std::shared_ptr<boost::container::flat_map<std::string, std::string>>
-        sensorToInvMap,
+    std::shared_ptr<std::vector<InventoryItem>> inventoryItems,
     Callback&& callback)
 {
     BMCWEB_LOG_DEBUG << "getInventoryItemsConnections enter";
 
     const std::string path = "/xyz/openbmc_project/inventory";
-    const std::array<std::string, 2> interfaces = {
+    const std::array<std::string, 4> interfaces = {
         "xyz.openbmc_project.Inventory.Item",
+        "xyz.openbmc_project.Inventory.Item.PowerSupply",
+        "xyz.openbmc_project.Inventory.Decorator.Asset",
         "xyz.openbmc_project.State.Decorator.OperationalStatus"};
 
     // Response handler for parsing output from GetSubTree
     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp,
-                        sensorToInvMap](const boost::system::error_code ec,
+                        inventoryItems](const boost::system::error_code ec,
                                         const GetSubTreeType& subtree) {
         BMCWEB_LOG_DEBUG << "getInventoryItemsConnections respHandler enter";
         if (ec)
@@ -1176,21 +1371,16 @@
                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
                  object : subtree)
         {
-            // Look for inventory item object path in the sensor->inventory map
+            // Check if object path is one of the specified inventory items
             const std::string& objPath = object.first;
-            for (const std::pair<std::string, std::string>& pair :
-                 *sensorToInvMap)
+            if (findInventoryItem(inventoryItems, objPath) != nullptr)
             {
-                if (pair.second == objPath)
+                // Store all connections to inventory item
+                for (const std::pair<std::string, std::vector<std::string>>&
+                         objData : object.second)
                 {
-                    // Store all connections to inventory item
-                    for (const std::pair<std::string, std::vector<std::string>>&
-                             objData : object.second)
-                    {
-                        const std::string& invConnection = objData.first;
-                        invConnections->insert(invConnection);
-                    }
-                    break;
+                    const std::string& invConnection = objData.first;
+                    invConnections->insert(invConnection);
                 }
             }
         }
@@ -1207,19 +1397,17 @@
 }
 
 /**
- * @brief Gets inventory items associated with the specified sensors.
+ * @brief Gets associations from sensors to inventory items.
  *
  * Looks for ObjectMapper associations from the specified sensors to related
- * inventory items.  Builds map where key is sensor object path and value is
- * inventory item object path.
+ * inventory items.
  *
  * Finds the inventory items asynchronously.  Invokes callback when information
  * has been obtained.
  *
  * The callback must have the following signature:
  *   @code
- *   callback(std::shared_ptr<boost::container::flat_map<
-                  std::string, std::string>> sensorToInvMap)
+ *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
  *   @endcode
  *
  * @param sensorsAsyncResp Pointer to object holding response data.
@@ -1229,32 +1417,33 @@
  * @param callback Callback to invoke when inventory items have been obtained.
  */
 template <typename Callback>
-static void getInventoryItems(
+static void getInventoryItemAssociations(
     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
         objectMgrPaths,
     Callback&& callback)
 {
-    BMCWEB_LOG_DEBUG << "getInventoryItems enter";
+    BMCWEB_LOG_DEBUG << "getInventoryItemAssociations enter";
 
     // Response handler for GetManagedObjects
     auto respHandler = [callback{std::move(callback)}, sensorsAsyncResp,
                         sensorNames](const boost::system::error_code ec,
                                      dbus::utility::ManagedObjectType& resp) {
-        BMCWEB_LOG_DEBUG << "getInventoryItems respHandler enter";
+        BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler enter";
         if (ec)
         {
-            BMCWEB_LOG_ERROR << "getInventoryItems respHandler DBus error "
-                             << ec;
+            BMCWEB_LOG_ERROR
+                << "getInventoryItemAssociations respHandler DBus error " << ec;
             messages::internalError(sensorsAsyncResp->res);
             return;
         }
 
+        // Create vector to hold list of inventory items
+        std::shared_ptr<std::vector<InventoryItem>> inventoryItems =
+            std::make_shared<std::vector<InventoryItem>>();
+
         // Loop through returned object paths
-        std::shared_ptr<boost::container::flat_map<std::string, std::string>>
-            sensorToInvMap = std::make_shared<
-                boost::container::flat_map<std::string, std::string>>();
         std::string sensorAssocPath;
         sensorAssocPath.reserve(128); // avoid memory allocations
         for (const auto& objDictEntry : resp)
@@ -1287,9 +1476,11 @@
                                     &endpointsIt->second);
                             if ((endpoints != nullptr) && !endpoints->empty())
                             {
-                                // Store sensor -> inventory item mapping
-                                const std::string& invItem = endpoints->front();
-                                (*sensorToInvMap)[sensorName] = invItem;
+                                // Add inventory item to vector
+                                const std::string& invItemPath =
+                                    endpoints->front();
+                                addInventoryItem(inventoryItems, invItemPath,
+                                                 sensorName);
                             }
                         }
                     }
@@ -1298,12 +1489,8 @@
             }
         }
 
-        // Call callback if at least one inventory item was found
-        if (!sensorToInvMap->empty())
-        {
-            callback(sensorToInvMap);
-        }
-        BMCWEB_LOG_DEBUG << "getInventoryItems respHandler exit";
+        callback(inventoryItems);
+        BMCWEB_LOG_DEBUG << "getInventoryItemAssociations respHandler exit";
     };
 
     // Find DBus object path that implements ObjectManager for ObjectMapper
@@ -1319,63 +1506,119 @@
         std::move(respHandler), connection, objectMgrPath,
         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
 
-    BMCWEB_LOG_DEBUG << "getInventoryItems exit";
+    BMCWEB_LOG_DEBUG << "getInventoryItemAssociations exit";
 }
 
 /**
- * @brief Checks the status of inventory items associated with sensors.
+ * @brief Gets inventory items associated with sensors.
  *
  * Finds the inventory items that are associated with the specified sensors.
- * Gets the status of those inventory items.
+ * Then gets D-Bus data for the inventory items, such as presence and VPD.
  *
- * If the inventory items are not present or functional, the sensor status is
- * updated in the JSON response.
+ * This data is later used to provide sensor property values in the JSON
+ * response.
  *
- * In D-Bus, the hardware present and functional properties are typically on the
- * inventory item rather than the sensor.
+ * Finds the inventory items asynchronously.  Invokes callback when the
+ * inventory items have been obtained.
+ *
+ * The callback must have the following signature:
+ *   @code
+ *   callback(std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
+ *   @endcode
  *
  * @param sensorsAsyncResp Pointer to object holding response data.
  * @param sensorNames All sensors within the current chassis.
  * @param objectMgrPaths Mappings from connection name to DBus object path that
  * implements ObjectManager.
+ * @param callback Callback to invoke when inventory items have been obtained.
  */
-static void checkInventoryItemsStatus(
+template <typename Callback>
+static void getInventoryItems(
     std::shared_ptr<SensorsAsyncResp> sensorsAsyncResp,
     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
-        objectMgrPaths)
+        objectMgrPaths,
+    Callback&& callback)
 {
-    BMCWEB_LOG_DEBUG << "checkInventoryItemsStatus enter";
-    auto getInventoryItemsCb =
-        [sensorsAsyncResp,
-         objectMgrPaths](std::shared_ptr<
-                         boost::container::flat_map<std::string, std::string>>
-                             sensorToInvMap) {
-            BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter";
+    BMCWEB_LOG_DEBUG << "getInventoryItems enter";
+    auto getInventoryItemAssociationsCb =
+        [sensorsAsyncResp, objectMgrPaths, callback{std::move(callback)}](
+            std::shared_ptr<std::vector<InventoryItem>> inventoryItems) {
+            BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb enter";
             auto getInventoryItemsConnectionsCb =
-                [sensorsAsyncResp, sensorToInvMap, objectMgrPaths](
+                [sensorsAsyncResp, inventoryItems, objectMgrPaths,
+                 callback{std::move(callback)}](
                     std::shared_ptr<boost::container::flat_set<std::string>>
                         invConnections) {
                     BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb enter";
 
-                    // Get status of inventory items and update sensors
-                    getInventoryItemsStatus(sensorsAsyncResp, sensorToInvMap,
-                                            invConnections, objectMgrPaths);
+                    // Get inventory item data from connections
+                    getInventoryItemsData(sensorsAsyncResp, inventoryItems,
+                                          invConnections, objectMgrPaths,
+                                          std::move(callback));
 
                     BMCWEB_LOG_DEBUG << "getInventoryItemsConnectionsCb exit";
                 };
 
-            // Get connections that provide status of inventory items
+            // Get connections that provide inventory item data
             getInventoryItemsConnections(
-                sensorsAsyncResp, sensorToInvMap,
+                sensorsAsyncResp, inventoryItems,
                 std::move(getInventoryItemsConnectionsCb));
-            BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit";
+            BMCWEB_LOG_DEBUG << "getInventoryItemAssociationsCb exit";
         };
 
-    // Get inventory items that are associated with specified sensors
-    getInventoryItems(sensorsAsyncResp, sensorNames, objectMgrPaths,
-                      std::move(getInventoryItemsCb));
-    BMCWEB_LOG_DEBUG << "checkInventoryItemsStatus exit";
+    // Get associations from sensors to inventory items
+    getInventoryItemAssociations(sensorsAsyncResp, sensorNames, objectMgrPaths,
+                                 std::move(getInventoryItemAssociationsCb));
+    BMCWEB_LOG_DEBUG << "getInventoryItems exit";
+}
+
+/**
+ * @brief Returns JSON PowerSupply object for the specified inventory item.
+ *
+ * Searches for a JSON PowerSupply object that matches the specified inventory
+ * item.  If one is not found, a new PowerSupply object is added to the JSON
+ * array.
+ *
+ * Multiple sensors are often associated with one power supply inventory item.
+ * As a result, multiple sensor values are stored in one JSON PowerSupply
+ * object.
+ *
+ * @param powerSupplyArray JSON array containing Redfish PowerSupply objects.
+ * @param inventoryItem Inventory item for the power supply.
+ * @param chassisId Chassis that contains the power supply.
+ * @return JSON PowerSupply object for the specified inventory item.
+ */
+static nlohmann::json& getPowerSupply(nlohmann::json& powerSupplyArray,
+                                      const InventoryItem& inventoryItem,
+                                      const std::string& chassisId)
+{
+    // Check if matching PowerSupply object already exists in JSON array
+    for (nlohmann::json& powerSupply : powerSupplyArray)
+    {
+        if (powerSupply["MemberId"] == inventoryItem.name)
+        {
+            return powerSupply;
+        }
+    }
+
+    // Add new PowerSupply object to JSON array
+    powerSupplyArray.push_back({});
+    nlohmann::json& powerSupply = powerSupplyArray.back();
+    powerSupply["@odata.id"] =
+        "/redfish/v1/Chassis/" + chassisId + "/Power#/PowerSupplies/";
+    powerSupply["MemberId"] = inventoryItem.name;
+    powerSupply["Name"] = boost::replace_all_copy(inventoryItem.name, "_", " ");
+    powerSupply["Manufacturer"] = inventoryItem.manufacturer;
+    powerSupply["Model"] = inventoryItem.model;
+    powerSupply["PartNumber"] = inventoryItem.partNumber;
+    powerSupply["SerialNumber"] = inventoryItem.serialNumber;
+    powerSupply["Status"]["State"] = getState(&inventoryItem);
+
+    const char* health = inventoryItem.isFunctional ? "OK" : "Critical";
+    powerSupply["Status"]["Health"] = health;
+
+    return powerSupply;
 }
 
 /**
@@ -1386,9 +1629,7 @@
  * Gets the sensor values asynchronously.  Stores the results later when the
  * information has been obtained.
  *
- * The sensorNames set contains all sensors for the current chassis.
- * SensorsAsyncResp contains the requested sensor types.  Only sensors of a
- * requested type are included in the JSON output.
+ * The sensorNames set contains all requested sensors for the current chassis.
  *
  * To minimize the number of DBus calls, the DBus method
  * org.freedesktop.DBus.ObjectManager.GetManagedObjects() is used to get the
@@ -1399,18 +1640,23 @@
  * The objectMgrPaths map contains mappings from a connection name to the
  * corresponding DBus object path that implements ObjectManager.
  *
+ * The InventoryItem vector contains D-Bus inventory items associated with the
+ * sensors.  Inventory item data is needed for some Redfish sensor properties.
+ *
  * @param SensorsAsyncResp Pointer to object holding response data.
- * @param sensorNames All sensors within the current chassis.
+ * @param sensorNames All requested sensors within the current chassis.
  * @param connections Connections that provide sensor values.
  * @param objectMgrPaths Mappings from connection name to DBus object path that
  * implements ObjectManager.
+ * @param inventoryItems Inventory items associated with the sensors.
  */
 void getSensorData(
     std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
     const std::shared_ptr<boost::container::flat_set<std::string>> sensorNames,
     const boost::container::flat_set<std::string>& connections,
     std::shared_ptr<boost::container::flat_map<std::string, std::string>>
-        objectMgrPaths)
+        objectMgrPaths,
+    std::shared_ptr<std::vector<InventoryItem>> inventoryItems)
 {
     BMCWEB_LOG_DEBUG << "getSensorData enter";
     // Get managed objects from all services exposing sensors
@@ -1418,7 +1664,7 @@
     {
         // Response handler to process managed objects
         auto getManagedObjectsCb = [SensorsAsyncResp, sensorNames,
-                                    objectMgrPaths](
+                                    inventoryItems](
                                        const boost::system::error_code ec,
                                        ManagedObjectsVectorType& resp) {
             BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter";
@@ -1459,6 +1705,10 @@
                     continue;
                 }
 
+                // Find inventory item (if any) associated with sensor
+                InventoryItem* inventoryItem =
+                    findInventoryItemForSensor(inventoryItems, objPath);
+
                 const char* fieldName = nullptr;
                 if (sensorType == "temperature")
                 {
@@ -1473,20 +1723,22 @@
                 {
                     fieldName = "Voltages";
                 }
-                else if (sensorType == "current")
-                {
-                    fieldName = "PowerSupplies";
-                }
                 else if (sensorType == "power")
                 {
                     if (!sensorName.compare("total_power"))
                     {
                         fieldName = "PowerControl";
                     }
-                    else
+                    else if ((inventoryItem != nullptr) &&
+                             (inventoryItem->isPowerSupply))
                     {
                         fieldName = "PowerSupplies";
                     }
+                    else
+                    {
+                        // Other power sensors are in SensorCollection
+                        continue;
+                    }
                 }
                 else
                 {
@@ -1497,24 +1749,31 @@
 
                 nlohmann::json& tempArray =
                     SensorsAsyncResp->res.jsonValue[fieldName];
+                nlohmann::json* sensorJson = nullptr;
 
-                if ((fieldName == "PowerSupplies" ||
-                     fieldName == "PowerControl") &&
-                    !tempArray.empty())
+                if (fieldName == "PowerControl")
                 {
-                    // For power supplies and power control put multiple
-                    // "sensors" into a single power supply or power control
-                    // entry, so only create the first one
+                    if (tempArray.empty())
+                    {
+                        // Put multiple "sensors" into a single PowerControl.
+                        // Follows MemberId naming and naming in power.hpp.
+                        tempArray.push_back(
+                            {{"@odata.id",
+                              "/redfish/v1/Chassis/" +
+                                  SensorsAsyncResp->chassisId + "/" +
+                                  SensorsAsyncResp->chassisSubNode + "#/" +
+                                  fieldName + "/0"}});
+                    }
+                    sensorJson = &(tempArray.back());
                 }
-                else if (fieldName == "PowerControl")
+                else if (fieldName == "PowerSupplies")
                 {
-                    // Put multiple "sensors" into a single PowerControl.
-                    // Follows MemberId naming and naming in power.hpp.
-                    tempArray.push_back(
-                        {{"@odata.id", "/redfish/v1/Chassis/" +
-                                           SensorsAsyncResp->chassisId + "/" +
-                                           SensorsAsyncResp->chassisSubNode +
-                                           "#/" + fieldName + "/0"}});
+                    if (inventoryItem != nullptr)
+                    {
+                        sensorJson =
+                            &(getPowerSupply(tempArray, *inventoryItem,
+                                             SensorsAsyncResp->chassisId));
+                    }
                 }
                 else
                 {
@@ -1523,17 +1782,19 @@
                                            SensorsAsyncResp->chassisId + "/" +
                                            SensorsAsyncResp->chassisSubNode +
                                            "#/" + fieldName + "/"}});
+                    sensorJson = &(tempArray.back());
                 }
-                nlohmann::json& sensorJson = tempArray.back();
 
-                objectInterfacesToJson(sensorName, sensorType,
-                                       objDictEntry.second, sensorJson);
+                if (sensorJson != nullptr)
+                {
+                    objectInterfacesToJson(sensorName, sensorType,
+                                           objDictEntry.second, *sensorJson,
+                                           inventoryItem);
+                }
             }
             if (SensorsAsyncResp.use_count() == 1)
             {
                 sortJSONResponse(SensorsAsyncResp);
-                checkInventoryItemsStatus(SensorsAsyncResp, sensorNames,
-                                          objectMgrPaths);
                 if (SensorsAsyncResp->chassisSubNode == "Thermal")
                 {
                     populateFanRedundancy(SensorsAsyncResp);
@@ -1580,10 +1841,24 @@
                                                                    std::string>>
                             objectMgrPaths) {
                         BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb enter";
-                        // Get sensor data and store results in JSON
-                        // response
-                        getSensorData(SensorsAsyncResp, sensorNames,
-                                      connections, objectMgrPaths);
+                        auto getInventoryItemsCb =
+                            [SensorsAsyncResp, sensorNames, connections,
+                             objectMgrPaths](
+                                std::shared_ptr<std::vector<InventoryItem>>
+                                    inventoryItems) {
+                                BMCWEB_LOG_DEBUG << "getInventoryItemsCb enter";
+                                // Get sensor data and store results in JSON
+                                getSensorData(SensorsAsyncResp, sensorNames,
+                                              connections, objectMgrPaths,
+                                              inventoryItems);
+                                BMCWEB_LOG_DEBUG << "getInventoryItemsCb exit";
+                            };
+
+                        // Get inventory items associated with sensors
+                        getInventoryItems(SensorsAsyncResp, sensorNames,
+                                          objectMgrPaths,
+                                          std::move(getInventoryItemsCb));
+
                         BMCWEB_LOG_DEBUG << "getObjectManagerPathsCb exit";
                     };