PID: Add fan profile support

This adds fan profiles to redfish. This uses the
Thermal Mode interface to allow switching between
different fan profiles. Only the selected fan profile
will be seen. When adding a new controller, they will
also be added to the configuration item for that profile.
Patching of the profile to switch between supported
profiles is also supported.

Tested: Could change profiles in redfish.

Python test script:

def testProfile():
    a = {
        "Oem": {
            "OpenBmc": {
                "Fan": {
                    "Profile" : "Acoustic"
                }
            }
        }
    }
    return a

def dopatch():
    resp = requests.patch(address, json=testProfile(), verify=False,
                          auth=("root", "0penBmc"))
    resp.raise_for_status()

Change-Id: Ie2d8582616ed5bde58e3328b21ba8c59437e88ce
Signed-off-by: James Feist <james.feist@linux.intel.com>
diff --git a/include/dbus_utility.hpp b/include/dbus_utility.hpp
index e45bb9a..bb2bd65 100644
--- a/include/dbus_utility.hpp
+++ b/include/dbus_utility.hpp
@@ -36,6 +36,11 @@
                   std::string,
                   boost::container::flat_map<std::string, DbusVariantType>>>>;
 
+using ManagedItem = std::pair<
+    sdbusplus::message::object_path,
+    boost::container::flat_map<
+        std::string, boost::container::flat_map<std::string, DbusVariantType>>>;
+
 inline void escapePathForDbus(std::string& path)
 {
     const std::regex reg("[^A-Za-z0-9_/]");
diff --git a/redfish-core/lib/managers.hpp b/redfish-core/lib/managers.hpp
index 5f5ef6e..829ce00 100644
--- a/redfish-core/lib/managers.hpp
+++ b/redfish-core/lib/managers.hpp
@@ -20,6 +20,7 @@
 #include <boost/algorithm/string/replace.hpp>
 #include <boost/date_time.hpp>
 #include <dbus_utility.hpp>
+#include <memory>
 #include <sstream>
 #include <utils/systemd_utils.hpp>
 #include <variant>
@@ -118,15 +119,20 @@
     "xyz.openbmc_project.Configuration.Pid.Zone";
 static constexpr const char* stepwiseConfigurationIface =
     "xyz.openbmc_project.Configuration.Stepwise";
+static constexpr const char* thermalModeIface =
+    "xyz.openbmc_project.Control.ThermalMode";
 
 static void asyncPopulatePid(const std::string& connection,
                              const std::string& path,
+                             const std::string& currentProfile,
+                             const std::vector<std::string>& supportedProfiles,
                              std::shared_ptr<AsyncResp> asyncResp)
 {
 
     crow::connections::systemBus->async_method_call(
-        [asyncResp](const boost::system::error_code ec,
-                    const dbus::utility::ManagedObjectType& managedObj) {
+        [asyncResp, currentProfile, supportedProfiles](
+            const boost::system::error_code ec,
+            const dbus::utility::ManagedObjectType& managedObj) {
             if (ec)
             {
                 BMCWEB_LOG_ERROR << ec;
@@ -168,6 +174,13 @@
             configRoot["@odata.type"] = "#OemManager.Fan";
             configRoot["@odata.context"] =
                 "/redfish/v1/$metadata#OemManager.Fan";
+            configRoot["Profile@Redfish.AllowableValues"] = supportedProfiles;
+
+            if (!currentProfile.empty())
+            {
+                configRoot["Profile"] = currentProfile;
+            }
+            BMCWEB_LOG_ERROR << "profile = " << currentProfile << " !";
 
             for (const auto& pathPair : managedObj)
             {
@@ -186,6 +199,7 @@
                         messages::internalError(asyncResp->res);
                         return;
                     }
+
                     const std::string* namePtr =
                         std::get_if<std::string>(&findName->second);
                     if (namePtr == nullptr)
@@ -194,9 +208,29 @@
                         messages::internalError(asyncResp->res);
                         return;
                     }
-
                     std::string name = *namePtr;
                     dbus::utility::escapePathForDbus(name);
+
+                    auto findProfiles = intfPair.second.find("Profiles");
+                    if (findProfiles != intfPair.second.end())
+                    {
+                        const std::vector<std::string>* profiles =
+                            std::get_if<std::vector<std::string>>(
+                                &findProfiles->second);
+                        if (profiles == nullptr)
+                        {
+                            BMCWEB_LOG_ERROR << "Pid Profiles Field illegal";
+                            messages::internalError(asyncResp->res);
+                            return;
+                        }
+                        if (std::find(profiles->begin(), profiles->end(),
+                                      currentProfile) == profiles->end())
+                        {
+                            BMCWEB_LOG_INFO
+                                << name << " not supported in current profile";
+                            continue;
+                        }
+                    }
                     nlohmann::json* config = nullptr;
 
                     const std::string* classPtr = nullptr;
@@ -522,8 +556,9 @@
     return true;
 }
 
-static bool findChassis(const dbus::utility::ManagedObjectType& managedObj,
-                        const std::string& value, std::string& chassis)
+static const dbus::utility::ManagedItem*
+    findChassis(const dbus::utility::ManagedObjectType& managedObj,
+                const std::string& value, std::string& chassis)
 {
     BMCWEB_LOG_DEBUG << "Find Chassis: " << value << "\n";
 
@@ -541,11 +576,16 @@
 
     if (it == managedObj.end())
     {
-        return false;
+        return nullptr;
     }
     // 5 comes from <chassis-name> being the 5th element
     // /xyz/openbmc_project/inventory/system/chassis/<chassis-name>
-    return dbus::utility::getNthStringFromPath(it->first.str, 5, chassis);
+    if (dbus::utility::getNthStringFromPath(it->first.str, 5, chassis))
+    {
+        return &(*it);
+    }
+
+    return nullptr;
 }
 
 static CreatePIDRet createPidInterface(
@@ -554,7 +594,7 @@
     const dbus::utility::ManagedObjectType& managedObj, bool createNewObject,
     boost::container::flat_map<std::string, dbus::utility::DbusVariantType>&
         output,
-    std::string& chassis)
+    std::string& chassis, const std::string& profile)
 {
 
     // common deleter
@@ -595,11 +635,13 @@
         return CreatePIDRet::del;
     }
 
+    const dbus::utility::ManagedItem* managedItem = nullptr;
     if (!createNewObject)
     {
         // if we aren't creating a new object, we should be able to find it on
         // d-bus
-        if (!findChassis(managedObj, it.key(), chassis))
+        managedItem = findChassis(managedObj, it.key(), chassis);
+        if (managedItem == nullptr)
         {
             BMCWEB_LOG_ERROR << "Failed to get chassis from config patch";
             messages::invalidObject(response->res, it.key());
@@ -607,6 +649,56 @@
         }
     }
 
+    if (profile.size() &&
+        (type == "PidControllers" || type == "FanControllers" ||
+         type == "StepwiseControllers"))
+    {
+        if (managedItem == nullptr)
+        {
+            output["Profiles"] = std::vector<std::string>{profile};
+        }
+        else
+        {
+            std::string interface;
+            if (type == "StepwiseControllers")
+            {
+                interface = stepwiseConfigurationIface;
+            }
+            else
+            {
+                interface = pidConfigurationIface;
+            }
+            auto findConfig = managedItem->second.find(interface);
+            if (findConfig == managedItem->second.end())
+            {
+                BMCWEB_LOG_ERROR
+                    << "Failed to find interface in managed object";
+                messages::internalError(response->res);
+                return CreatePIDRet::fail;
+            }
+            auto findProfiles = findConfig->second.find("Profiles");
+            if (findProfiles != findConfig->second.end())
+            {
+                const std::vector<std::string>* curProfiles =
+                    std::get_if<std::vector<std::string>>(
+                        &(findProfiles->second));
+                if (curProfiles == nullptr)
+                {
+                    BMCWEB_LOG_ERROR << "Illegal profiles in managed object";
+                    messages::internalError(response->res);
+                    return CreatePIDRet::fail;
+                }
+                if (std::find(curProfiles->begin(), curProfiles->end(),
+                              profile) == curProfiles->end())
+                {
+                    std::vector<std::string> newProfiles = *curProfiles;
+                    newProfiles.push_back(profile);
+                    output["Profiles"] = newProfiles;
+                }
+            }
+        }
+    }
+
     if (type == "PidControllers" || type == "FanControllers")
     {
         if (createNewObject)
@@ -846,6 +938,510 @@
     }
     return CreatePIDRet::patch;
 }
+struct GetPIDValues : std::enable_shared_from_this<GetPIDValues>
+{
+
+    GetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp) :
+        asyncResp(asyncResp)
+
+    {
+    }
+
+    void run()
+    {
+        std::shared_ptr<GetPIDValues> self = shared_from_this();
+
+        // get all configurations
+        crow::connections::systemBus->async_method_call(
+            [self](const boost::system::error_code ec,
+                   const crow::openbmc_mapper::GetSubTreeType& subtree) {
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR << ec;
+                    messages::internalError(self->asyncResp->res);
+                    return;
+                }
+                self->subtree = subtree;
+            },
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
+            std::array<const char*, 4>{
+                pidConfigurationIface, pidZoneConfigurationIface,
+                objectManagerIface, stepwiseConfigurationIface});
+
+        // at the same time get the selected profile
+        crow::connections::systemBus->async_method_call(
+            [self](const boost::system::error_code ec,
+                   const crow::openbmc_mapper::GetSubTreeType& subtree) {
+                if (ec || subtree.empty())
+                {
+                    return;
+                }
+                if (subtree[0].second.size() != 1)
+                {
+                    // invalid mapper response, should never happen
+                    BMCWEB_LOG_ERROR << "GetPIDValues: Mapper Error";
+                    messages::internalError(self->asyncResp->res);
+                    return;
+                }
+
+                const std::string& path = subtree[0].first;
+                const std::string& owner = subtree[0].second[0].first;
+                crow::connections::systemBus->async_method_call(
+                    [path, owner, self](
+                        const boost::system::error_code ec,
+                        const boost::container::flat_map<
+                            std::string, std::variant<std::vector<std::string>,
+                                                      std::string>>& resp) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_ERROR << "GetPIDValues: Can't get "
+                                                "thermalModeIface "
+                                             << path;
+                            messages::internalError(self->asyncResp->res);
+                            return;
+                        }
+                        const std::string* current;
+                        const std::vector<std::string>* supported;
+                        for (auto& [key, value] : resp)
+                        {
+                            if (key == "Current")
+                            {
+                                current = std::get_if<std::string>(&value);
+                                if (current == nullptr)
+                                {
+                                    BMCWEB_LOG_ERROR
+                                        << "GetPIDValues: thermal mode "
+                                           "iface invalid "
+                                        << path;
+                                    messages::internalError(
+                                        self->asyncResp->res);
+                                    return;
+                                }
+                            }
+                            if (key == "Supported")
+                            {
+                                supported =
+                                    std::get_if<std::vector<std::string>>(
+                                        &value);
+                                if (supported == nullptr)
+                                {
+                                    BMCWEB_LOG_ERROR
+                                        << "GetPIDValues: thermal mode "
+                                           "iface invalid"
+                                        << path;
+                                    messages::internalError(
+                                        self->asyncResp->res);
+                                    return;
+                                }
+                            }
+                        }
+                        if (current == nullptr || supported == nullptr)
+                        {
+                            BMCWEB_LOG_ERROR << "GetPIDValues: thermal mode "
+                                                "iface invalid "
+                                             << path;
+                            messages::internalError(self->asyncResp->res);
+                            return;
+                        }
+                        self->currentProfile = *current;
+                        self->supportedProfiles = *supported;
+                    },
+                    owner, path, "org.freedesktop.DBus.Properties", "GetAll",
+                    thermalModeIface);
+            },
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
+            std::array<const char*, 1>{thermalModeIface});
+    }
+
+    ~GetPIDValues()
+    {
+        if (asyncResp->res.result() != boost::beast::http::status::ok)
+        {
+            return;
+        }
+        // create map of <connection, path to objMgr>>
+        boost::container::flat_map<std::string, std::string> objectMgrPaths;
+        boost::container::flat_set<std::string> calledConnections;
+        for (const auto& pathGroup : subtree)
+        {
+            for (const auto& connectionGroup : pathGroup.second)
+            {
+                auto findConnection =
+                    calledConnections.find(connectionGroup.first);
+                if (findConnection != calledConnections.end())
+                {
+                    break;
+                }
+                for (const std::string& interface : connectionGroup.second)
+                {
+                    if (interface == objectManagerIface)
+                    {
+                        objectMgrPaths[connectionGroup.first] = pathGroup.first;
+                    }
+                    // this list is alphabetical, so we
+                    // should have found the objMgr by now
+                    if (interface == pidConfigurationIface ||
+                        interface == pidZoneConfigurationIface ||
+                        interface == stepwiseConfigurationIface)
+                    {
+                        auto findObjMgr =
+                            objectMgrPaths.find(connectionGroup.first);
+                        if (findObjMgr == objectMgrPaths.end())
+                        {
+                            BMCWEB_LOG_DEBUG << connectionGroup.first
+                                             << "Has no Object Manager";
+                            continue;
+                        }
+
+                        calledConnections.insert(connectionGroup.first);
+
+                        asyncPopulatePid(findObjMgr->first, findObjMgr->second,
+                                         currentProfile, supportedProfiles,
+                                         asyncResp);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    std::vector<std::string> supportedProfiles;
+    std::string currentProfile;
+    crow::openbmc_mapper::GetSubTreeType subtree;
+    std::shared_ptr<AsyncResp> asyncResp;
+};
+
+struct SetPIDValues : std::enable_shared_from_this<SetPIDValues>
+{
+
+    SetPIDValues(const std::shared_ptr<AsyncResp>& asyncResp,
+                 nlohmann::json& data) :
+        asyncResp(asyncResp)
+    {
+
+        std::optional<nlohmann::json> pidControllers;
+        std::optional<nlohmann::json> fanControllers;
+        std::optional<nlohmann::json> fanZones;
+        std::optional<nlohmann::json> stepwiseControllers;
+
+        if (!redfish::json_util::readJson(
+                data, asyncResp->res, "PidControllers", pidControllers,
+                "FanControllers", fanControllers, "FanZones", fanZones,
+                "StepwiseControllers", stepwiseControllers, "Profile", profile))
+        {
+            BMCWEB_LOG_ERROR << "Line:" << __LINE__ << ", Illegal Property "
+                             << data.dump();
+            return;
+        }
+        configuration.emplace_back("PidControllers", std::move(pidControllers));
+        configuration.emplace_back("FanControllers", std::move(fanControllers));
+        configuration.emplace_back("FanZones", std::move(fanZones));
+        configuration.emplace_back("StepwiseControllers",
+                                   std::move(stepwiseControllers));
+    }
+    void run()
+    {
+        if (asyncResp->res.result() != boost::beast::http::status::ok)
+        {
+            return;
+        }
+
+        std::shared_ptr<SetPIDValues> self = shared_from_this();
+
+        // todo(james): might make sense to do a mapper call here if this
+        // interface gets more traction
+        crow::connections::systemBus->async_method_call(
+            [self](const boost::system::error_code ec,
+                   dbus::utility::ManagedObjectType& managedObj) {
+                if (ec)
+                {
+                    BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
+                    messages::internalError(self->asyncResp->res);
+                    return;
+                }
+                self->managedObj = std::move(managedObj);
+            },
+            "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
+            "GetManagedObjects");
+
+        // at the same time get the profile information
+        crow::connections::systemBus->async_method_call(
+            [self](const boost::system::error_code ec,
+                   const crow::openbmc_mapper::GetSubTreeType& subtree) {
+                if (ec || subtree.empty())
+                {
+                    return;
+                }
+                if (subtree[0].second.empty())
+                {
+                    // invalid mapper response, should never happen
+                    BMCWEB_LOG_ERROR << "SetPIDValues: Mapper Error";
+                    messages::internalError(self->asyncResp->res);
+                    return;
+                }
+
+                const std::string& path = subtree[0].first;
+                const std::string& owner = subtree[0].second[0].first;
+                crow::connections::systemBus->async_method_call(
+                    [self, path, owner](
+                        const boost::system::error_code ec,
+                        const boost::container::flat_map<
+                            std::string, std::variant<std::vector<std::string>,
+                                                      std::string>>& resp) {
+                        if (ec)
+                        {
+                            BMCWEB_LOG_ERROR << "SetPIDValues: Can't get "
+                                                "thermalModeIface "
+                                             << path;
+                            messages::internalError(self->asyncResp->res);
+                            return;
+                        }
+                        const std::string* current;
+                        const std::vector<std::string>* supported;
+                        for (auto& [key, value] : resp)
+                        {
+                            if (key == "Current")
+                            {
+                                current = std::get_if<std::string>(&value);
+                                if (current == nullptr)
+                                {
+                                    BMCWEB_LOG_ERROR
+                                        << "SetPIDValues: thermal mode "
+                                           "iface invalid "
+                                        << path;
+                                    messages::internalError(
+                                        self->asyncResp->res);
+                                    return;
+                                }
+                            }
+                            if (key == "Supported")
+                            {
+                                supported =
+                                    std::get_if<std::vector<std::string>>(
+                                        &value);
+                                if (supported == nullptr)
+                                {
+                                    BMCWEB_LOG_ERROR
+                                        << "SetPIDValues: thermal mode "
+                                           "iface invalid"
+                                        << path;
+                                    messages::internalError(
+                                        self->asyncResp->res);
+                                    return;
+                                }
+                            }
+                        }
+                        if (current == nullptr || supported == nullptr)
+                        {
+                            BMCWEB_LOG_ERROR << "SetPIDValues: thermal mode "
+                                                "iface invalid "
+                                             << path;
+                            messages::internalError(self->asyncResp->res);
+                            return;
+                        }
+                        self->currentProfile = *current;
+                        self->supportedProfiles = *supported;
+                        self->profileConnection = owner;
+                        self->profilePath = path;
+                    },
+                    owner, path, "org.freedesktop.DBus.Properties", "GetAll",
+                    thermalModeIface);
+            },
+            "xyz.openbmc_project.ObjectMapper",
+            "/xyz/openbmc_project/object_mapper",
+            "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
+            std::array<const char*, 1>{thermalModeIface});
+    }
+    ~SetPIDValues()
+    {
+        if (asyncResp->res.result() != boost::beast::http::status::ok)
+        {
+            return;
+        }
+
+        std::shared_ptr<AsyncResp> response = asyncResp;
+
+        if (profile)
+        {
+            if (std::find(supportedProfiles.begin(), supportedProfiles.end(),
+                          *profile) == supportedProfiles.end())
+            {
+                messages::actionParameterUnknown(response->res, "Profile",
+                                                 *profile);
+                return;
+            }
+            currentProfile = *profile;
+            crow::connections::systemBus->async_method_call(
+                [response](const boost::system::error_code ec) {
+                    if (ec)
+                    {
+                        BMCWEB_LOG_ERROR << "Error patching profile" << ec;
+                        messages::internalError(response->res);
+                    }
+                },
+                profileConnection, profilePath,
+                "org.freedesktop.DBus.Properties", "Set", thermalModeIface,
+                "Current", std::variant<std::string>(*profile));
+        }
+
+        for (auto& containerPair : configuration)
+        {
+            auto& container = containerPair.second;
+            if (!container)
+            {
+                continue;
+            }
+            std::string& type = containerPair.first;
+
+            for (nlohmann::json::iterator it = container->begin();
+                 it != container->end(); it++)
+            {
+                const auto& name = it.key();
+                auto pathItr =
+                    std::find_if(managedObj.begin(), managedObj.end(),
+                                 [&name](const auto& obj) {
+                                     return boost::algorithm::ends_with(
+                                         obj.first.str, "/" + name);
+                                 });
+                boost::container::flat_map<std::string,
+                                           dbus::utility::DbusVariantType>
+                    output;
+
+                output.reserve(16); // The pid interface length
+
+                // determines if we're patching entity-manager or
+                // creating a new object
+                bool createNewObject = (pathItr == managedObj.end());
+                std::string iface;
+                if (type == "PidControllers" || type == "FanControllers")
+                {
+                    iface = pidConfigurationIface;
+                    if (!createNewObject &&
+                        pathItr->second.find(pidConfigurationIface) ==
+                            pathItr->second.end())
+                    {
+                        createNewObject = true;
+                    }
+                }
+                else if (type == "FanZones")
+                {
+                    iface = pidZoneConfigurationIface;
+                    if (!createNewObject &&
+                        pathItr->second.find(pidZoneConfigurationIface) ==
+                            pathItr->second.end())
+                    {
+
+                        createNewObject = true;
+                    }
+                }
+                else if (type == "StepwiseControllers")
+                {
+                    iface = stepwiseConfigurationIface;
+                    if (!createNewObject &&
+                        pathItr->second.find(stepwiseConfigurationIface) ==
+                            pathItr->second.end())
+                    {
+                        createNewObject = true;
+                    }
+                }
+                BMCWEB_LOG_DEBUG << "Create new = " << createNewObject << "\n";
+                output["Name"] = boost::replace_all_copy(name, "_", " ");
+
+                std::string chassis;
+                CreatePIDRet ret = createPidInterface(
+                    response, type, it, pathItr->first.str, managedObj,
+                    createNewObject, output, chassis, currentProfile);
+                if (ret == CreatePIDRet::fail)
+                {
+                    return;
+                }
+                else if (ret == CreatePIDRet::del)
+                {
+                    continue;
+                }
+
+                if (!createNewObject)
+                {
+                    for (const auto& property : output)
+                    {
+                        crow::connections::systemBus->async_method_call(
+                            [response,
+                             propertyName{std::string(property.first)}](
+                                const boost::system::error_code ec) {
+                                if (ec)
+                                {
+                                    BMCWEB_LOG_ERROR << "Error patching "
+                                                     << propertyName << ": "
+                                                     << ec;
+                                    messages::internalError(response->res);
+                                    return;
+                                }
+                                messages::success(response->res);
+                            },
+                            "xyz.openbmc_project.EntityManager",
+                            pathItr->first.str,
+                            "org.freedesktop.DBus.Properties", "Set", iface,
+                            property.first, property.second);
+                    }
+                }
+                else
+                {
+                    if (chassis.empty())
+                    {
+                        BMCWEB_LOG_ERROR << "Failed to get chassis from config";
+                        messages::invalidObject(response->res, name);
+                        return;
+                    }
+
+                    bool foundChassis = false;
+                    for (const auto& obj : managedObj)
+                    {
+                        if (boost::algorithm::ends_with(obj.first.str, chassis))
+                        {
+                            chassis = obj.first.str;
+                            foundChassis = true;
+                            break;
+                        }
+                    }
+                    if (!foundChassis)
+                    {
+                        BMCWEB_LOG_ERROR << "Failed to find chassis on dbus";
+                        messages::resourceMissingAtURI(
+                            response->res, "/redfish/v1/Chassis/" + chassis);
+                        return;
+                    }
+
+                    crow::connections::systemBus->async_method_call(
+                        [response](const boost::system::error_code ec) {
+                            if (ec)
+                            {
+                                BMCWEB_LOG_ERROR << "Error Adding Pid Object "
+                                                 << ec;
+                                messages::internalError(response->res);
+                                return;
+                            }
+                            messages::success(response->res);
+                        },
+                        "xyz.openbmc_project.EntityManager", chassis,
+                        "xyz.openbmc_project.AddObject", "AddObject", output);
+                }
+            }
+        }
+    }
+    std::shared_ptr<AsyncResp> asyncResp;
+    std::vector<std::pair<std::string, std::optional<nlohmann::json>>>
+        configuration;
+    std::optional<std::string> profile;
+    dbus::utility::ManagedObjectType managedObj;
+    std::vector<std::string> supportedProfiles;
+    std::string currentProfile;
+    std::string profileConnection;
+    std::string profilePath;
+};
 
 class Manager : public Node
 {
@@ -864,73 +1460,6 @@
     }
 
   private:
-    void getPidValues(std::shared_ptr<AsyncResp> asyncResp)
-    {
-        crow::connections::systemBus->async_method_call(
-            [asyncResp](const boost::system::error_code ec,
-                        const crow::openbmc_mapper::GetSubTreeType& subtree) {
-                if (ec)
-                {
-                    BMCWEB_LOG_ERROR << ec;
-                    messages::internalError(asyncResp->res);
-                    return;
-                }
-
-                // create map of <connection, path to objMgr>>
-                boost::container::flat_map<std::string, std::string>
-                    objectMgrPaths;
-                boost::container::flat_set<std::string> calledConnections;
-                for (const auto& pathGroup : subtree)
-                {
-                    for (const auto& connectionGroup : pathGroup.second)
-                    {
-                        auto findConnection =
-                            calledConnections.find(connectionGroup.first);
-                        if (findConnection != calledConnections.end())
-                        {
-                            break;
-                        }
-                        for (const std::string& interface :
-                             connectionGroup.second)
-                        {
-                            if (interface == objectManagerIface)
-                            {
-                                objectMgrPaths[connectionGroup.first] =
-                                    pathGroup.first;
-                            }
-                            // this list is alphabetical, so we
-                            // should have found the objMgr by now
-                            if (interface == pidConfigurationIface ||
-                                interface == pidZoneConfigurationIface ||
-                                interface == stepwiseConfigurationIface)
-                            {
-                                auto findObjMgr =
-                                    objectMgrPaths.find(connectionGroup.first);
-                                if (findObjMgr == objectMgrPaths.end())
-                                {
-                                    BMCWEB_LOG_DEBUG << connectionGroup.first
-                                                     << "Has no Object Manager";
-                                    continue;
-                                }
-
-                                calledConnections.insert(connectionGroup.first);
-
-                                asyncPopulatePid(findObjMgr->first,
-                                                 findObjMgr->second, asyncResp);
-                                break;
-                            }
-                        }
-                    }
-                }
-            },
-            "xyz.openbmc_project.ObjectMapper",
-            "/xyz/openbmc_project/object_mapper",
-            "xyz.openbmc_project.ObjectMapper", "GetSubTree", "/", 0,
-            std::array<const char*, 4>{
-                pidConfigurationIface, pidZoneConfigurationIface,
-                objectManagerIface, stepwiseConfigurationIface});
-    }
-
     void doGet(crow::Response& res, const crow::Request& req,
                const std::vector<std::string>& params) override
     {
@@ -1035,208 +1564,8 @@
             "xyz.openbmc_project.Software.BMC.Updater",
             "/xyz/openbmc_project/software",
             "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
-        getPidValues(asyncResp);
-    }
-    void setPidValues(std::shared_ptr<AsyncResp> response, nlohmann::json& data)
-    {
-
-        // todo(james): might make sense to do a mapper call here if this
-        // interface gets more traction
-        crow::connections::systemBus->async_method_call(
-            [response,
-             data](const boost::system::error_code ec,
-                   const dbus::utility::ManagedObjectType& managedObj) {
-                if (ec)
-                {
-                    BMCWEB_LOG_ERROR << "Error communicating to Entity Manager";
-                    messages::internalError(response->res);
-                    return;
-                }
-
-                // todo(james) mutable doesn't work with asio bindings
-                nlohmann::json jsonData = data;
-
-                std::optional<nlohmann::json> pidControllers;
-                std::optional<nlohmann::json> fanControllers;
-                std::optional<nlohmann::json> fanZones;
-                std::optional<nlohmann::json> stepwiseControllers;
-                if (!redfish::json_util::readJson(
-                        jsonData, response->res, "PidControllers",
-                        pidControllers, "FanControllers", fanControllers,
-                        "FanZones", fanZones, "StepwiseControllers",
-                        stepwiseControllers))
-                {
-                    BMCWEB_LOG_ERROR << "Line:" << __LINE__
-                                     << ", Illegal Property "
-                                     << jsonData.dump();
-                    return;
-                }
-                std::array<
-                    std::pair<std::string, std::optional<nlohmann::json>*>, 4>
-                    sections = {
-                        std::make_pair("PidControllers", &pidControllers),
-                        std::make_pair("FanControllers", &fanControllers),
-                        std::make_pair("FanZones", &fanZones),
-                        std::make_pair("StepwiseControllers",
-                                       &stepwiseControllers)};
-
-                for (auto& containerPair : sections)
-                {
-                    auto& container = *(containerPair.second);
-                    if (!container)
-                    {
-                        continue;
-                    }
-                    std::string& type = containerPair.first;
-
-                    for (nlohmann::json::iterator it = container->begin();
-                         it != container->end(); it++)
-                    {
-                        const auto& name = it.key();
-                        auto pathItr =
-                            std::find_if(managedObj.begin(), managedObj.end(),
-                                         [&name](const auto& obj) {
-                                             return boost::algorithm::ends_with(
-                                                 obj.first.str, "/" + name);
-                                         });
-                        boost::container::flat_map<
-                            std::string, dbus::utility::DbusVariantType>
-                            output;
-
-                        output.reserve(16); // The pid interface length
-
-                        // determines if we're patching entity-manager or
-                        // creating a new object
-                        bool createNewObject = (pathItr == managedObj.end());
-                        std::string iface;
-                        if (type == "PidControllers" ||
-                            type == "FanControllers")
-                        {
-                            iface = pidConfigurationIface;
-                            if (!createNewObject &&
-                                pathItr->second.find(pidConfigurationIface) ==
-                                    pathItr->second.end())
-                            {
-                                createNewObject = true;
-                            }
-                        }
-                        else if (type == "FanZones")
-                        {
-                            iface = pidZoneConfigurationIface;
-                            if (!createNewObject &&
-                                pathItr->second.find(
-                                    pidZoneConfigurationIface) ==
-                                    pathItr->second.end())
-                            {
-
-                                createNewObject = true;
-                            }
-                        }
-                        else if (type == "StepwiseControllers")
-                        {
-                            iface = stepwiseConfigurationIface;
-                            if (!createNewObject &&
-                                pathItr->second.find(
-                                    stepwiseConfigurationIface) ==
-                                    pathItr->second.end())
-                            {
-                                createNewObject = true;
-                            }
-                        }
-                        BMCWEB_LOG_DEBUG << "Create new = " << createNewObject
-                                         << "\n";
-                        output["Name"] =
-                            boost::replace_all_copy(name, "_", " ");
-
-                        std::string chassis;
-                        CreatePIDRet ret = createPidInterface(
-                            response, type, it, pathItr->first.str, managedObj,
-                            createNewObject, output, chassis);
-                        if (ret == CreatePIDRet::fail)
-                        {
-                            return;
-                        }
-                        else if (ret == CreatePIDRet::del)
-                        {
-                            continue;
-                        }
-
-                        if (!createNewObject)
-                        {
-                            for (const auto& property : output)
-                            {
-                                crow::connections::systemBus->async_method_call(
-                                    [response,
-                                     propertyName{std::string(property.first)}](
-                                        const boost::system::error_code ec) {
-                                        if (ec)
-                                        {
-                                            BMCWEB_LOG_ERROR
-                                                << "Error patching "
-                                                << propertyName << ": " << ec;
-                                            messages::internalError(
-                                                response->res);
-                                            return;
-                                        }
-                                        messages::success(response->res);
-                                    },
-                                    "xyz.openbmc_project.EntityManager",
-                                    pathItr->first.str,
-                                    "org.freedesktop.DBus.Properties", "Set",
-                                    iface, property.first, property.second);
-                            }
-                        }
-                        else
-                        {
-                            if (chassis.empty())
-                            {
-                                BMCWEB_LOG_ERROR
-                                    << "Failed to get chassis from config";
-                                messages::invalidObject(response->res, name);
-                                return;
-                            }
-
-                            bool foundChassis = false;
-                            for (const auto& obj : managedObj)
-                            {
-                                if (boost::algorithm::ends_with(obj.first.str,
-                                                                chassis))
-                                {
-                                    chassis = obj.first.str;
-                                    foundChassis = true;
-                                    break;
-                                }
-                            }
-                            if (!foundChassis)
-                            {
-                                BMCWEB_LOG_ERROR
-                                    << "Failed to find chassis on dbus";
-                                messages::resourceMissingAtURI(
-                                    response->res,
-                                    "/redfish/v1/Chassis/" + chassis);
-                                return;
-                            }
-
-                            crow::connections::systemBus->async_method_call(
-                                [response](const boost::system::error_code ec) {
-                                    if (ec)
-                                    {
-                                        BMCWEB_LOG_ERROR
-                                            << "Error Adding Pid Object " << ec;
-                                        messages::internalError(response->res);
-                                        return;
-                                    }
-                                    messages::success(response->res);
-                                },
-                                "xyz.openbmc_project.EntityManager", chassis,
-                                "xyz.openbmc_project.AddObject", "AddObject",
-                                output);
-                        }
-                    }
-                }
-            },
-            "xyz.openbmc_project.EntityManager", "/", objectManagerIface,
-            "GetManagedObjects");
+        auto pids = std::make_shared<GetPIDValues>(asyncResp);
+        pids->run();
     }
 
     void doPatch(crow::Response& res, const crow::Request& req,
@@ -1273,7 +1602,8 @@
                 }
                 if (fan)
                 {
-                    setPidValues(response, *fan);
+                    auto pid = std::make_shared<SetPIDValues>(response, *fan);
+                    pid->run();
                 }
             }
         }