Allow disabling PID loops at runtime

<design concept>
Add the map of object enable interface to pid loops
in the zone then we can disable/enable each pid loop
process in a zone by dbus command.

[note]
Enable = true  : enable process (default)
Enable = false : disable process

Tested:
In this case: we set Enable = false to disable
pidloop:Zone_Temp_0, and see how it affects
the zone final pwm, when pidloop: Zone_Temp_0
in zone 0 is disabled.

then even we are trying to heat up the temperature
of a sensor: Temp_0 in pidloop: Zone_Temp_0, this
set point of the pidloop will not be taken into the
calculation for the final set point of the whole zone.

```
<service object>
root@openbmc:/tmp# busctl tree xyz.openbmc_project.State.FanCtrl
`-/xyz
  `-/xyz/openbmc_project
    `-/xyz/openbmc_project/settings
      `-/xyz/openbmc_project/settings/fanctrl
        |-/xyz/openbmc_project/settings/fanctrl/zone0
        | |-/xyz/openbmc_project/settings/fanctrl/zone0/Zone_Temp
        | |-/xyz/openbmc_project/settings/fanctrl/zone0/Zone_Temp_0
        | `-/xyz/openbmc_project/settings/fanctrl/zone0/Zone_Temp_1

====Enable process for pidloop:Zone_Temp_0 with p-switch temperature sensor:Temp_0 at runtime====
root@openbmc:~# busctl introspect xyz.openbmc_project.State.FanCtrl /xyz/openbmc_project/settings/fanctrl/zone0/Zone_Temp_0
NAME                                TYPE      SIGNATURE RESULT/VALUE FLAGS
xyz.openbmc_project.Object.Enable   interface -         -            -
.Enabled                            property  b         true         emits-change writable

====Disable process for pidloop:Zone_Temp_0 with p-switch temperature sensor: Temp_0====
root@openbmc:~# busctl set-property xyz.openbmc_project.State.FanCtrl /xyz/openbmc_project/settings/fanctrl/zone0/Zone_Temp_0 xyz.openbmc_project.Object.Enable Enabled b false
root@openbmc:~# busctl introspect xyz.openbmc_project.State.FanCtrl /xyz/openbmc_project/settings/fanctrl/zone0/Zone_Temp_0
NAME                                TYPE      SIGNATURE RESULT/VALUE FLAGS
xyz.openbmc_project.Object.Enable   interface -         -            -
.Enabled                            property  b         false        emits-change writable
```

when Disable the process of the pidloop: Zone_Temp_0,
the requester switches from Zone_Temp_0 to the others,
when you enable the pidloop: Zone_Temp_0, the setpoint
of Zone_Temp_0 will be take into consideration again

Change-Id: I95ae700144f0d16049fff8b309f05ae690a7ef72
Signed-off-by: ykchiu <Chiu.YK@inventec.com>
diff --git a/dbus/dbusconfiguration.cpp b/dbus/dbusconfiguration.cpp
index 7bb96f1..4f86ff6 100644
--- a/dbus/dbusconfiguration.cpp
+++ b/dbus/dbusconfiguration.cpp
@@ -634,7 +634,8 @@
         {
             const auto& base =
                 configuration.second.at(pidConfigurationInterface);
-            const std::string pidName = std::get<std::string>(base.at("Name"));
+            const std::string pidName =
+                sensorNameToDbusName(std::get<std::string>(base.at("Name")));
             const std::string pidClass =
                 std::get<std::string>(base.at("Class"));
             const std::vector<std::string>& zones =
@@ -833,8 +834,7 @@
 
                 if (offsetType.empty())
                 {
-                    conf::ControllerInfo& info =
-                        conf[std::get<std::string>(base.at("Name"))];
+                    conf::ControllerInfo& info = conf[pidName];
                     info.inputs = std::move(inputSensorNames);
                     populatePidInfo(bus, base, info, nullptr, sensorConfig);
                 }
@@ -857,6 +857,8 @@
         if (findStepwise != configuration.second.end())
         {
             const auto& base = findStepwise->second;
+            const std::string pidName =
+                sensorNameToDbusName(std::get<std::string>(base.at("Name")));
             const std::vector<std::string>& zones =
                 std::get<std::vector<std::string>>(base.at("Zones"));
             for (const std::string& zone : zones)
@@ -913,8 +915,7 @@
                 {
                     continue;
                 }
-                conf::ControllerInfo& info =
-                    conf[std::get<std::string>(base.at("Name"))];
+                conf::ControllerInfo& info = conf[pidName];
                 info.inputs = std::move(inputs);
 
                 info.type = "stepwise";
diff --git a/interfaces.hpp b/interfaces.hpp
index 5e99d19..7f4b4cd 100644
--- a/interfaces.hpp
+++ b/interfaces.hpp
@@ -7,7 +7,7 @@
 
 struct ReadReturn
 {
-    double value;
+    double value = std::numeric_limits<double>::quiet_NaN();
     std::chrono::high_resolution_clock::time_point updated;
     double unscaled = value;
 
diff --git a/pid/builder.cpp b/pid/builder.cpp
index d7a60b4..08526fa 100644
--- a/pid/builder.cpp
+++ b/pid/builder.cpp
@@ -44,6 +44,11 @@
     return std::string(objectPath) + std::to_string(zone);
 }
 
+static std::string getPidControlPath(int64_t zone, std::string pidname)
+{
+    return std::string(objectPath) + std::to_string(zone) + "/" + pidname;
+}
+
 std::unordered_map<int64_t, std::shared_ptr<ZoneInterface>>
     buildZones(const std::map<int64_t, conf::PIDConf>& zonePids,
                std::map<int64_t, conf::ZoneConfig>& zoneConfigs,
@@ -109,6 +114,9 @@
                     getThermalType(info.type));
 
                 zone->addThermalPID(std::move(pid));
+                zone->addPidControlProcess(name, modeControlBus,
+                                           getPidControlPath(zoneId, name),
+                                           deferSignals);
             }
             else if (info.type == "stepwise")
             {
@@ -120,6 +128,9 @@
                 auto stepwise = StepwiseController::createStepwiseController(
                     zone.get(), name, inputs, info.stepwiseInfo);
                 zone->addThermalPID(std::move(stepwise));
+                zone->addPidControlProcess(name, modeControlBus,
+                                           getPidControlPath(zoneId, name),
+                                           deferSignals);
             }
 
             std::cerr << "inputs: ";
diff --git a/pid/buildjson.cpp b/pid/buildjson.cpp
index c37a5f6..1122f09 100644
--- a/pid/buildjson.cpp
+++ b/pid/buildjson.cpp
@@ -187,6 +187,12 @@
             auto name = pid["name"];
             auto item = pid.get<conf::ControllerInfo>();
 
+            if (thisZone.find(name) != thisZone.end())
+            {
+                std::cerr << "Warning: zone " << id
+                          << " have the same pid name " << name << std::endl;
+            }
+
             thisZone[name] = item;
         }
 
diff --git a/pid/zone.cpp b/pid/zone.cpp
index c88f41b..b76fdec 100644
--- a/pid/zone.cpp
+++ b/pid/zone.cpp
@@ -103,6 +103,12 @@
 
 void DbusPidZone::addSetPoint(double setPoint, const std::string& name)
 {
+    /* exclude disabled pidloop from _maximumSetPoint calculation*/
+    if (!isPidProcessEnabled(name))
+    {
+        return;
+    }
+
     _SetPoints.push_back(setPoint);
     /*
      * if there are multiple thermal controllers with the same
@@ -129,6 +135,7 @@
 {
     _SetPoints.clear();
     _maximumSetPoint = 0;
+    _maximumSetPointName.clear();
 }
 
 double DbusPidZone::getFailSafePercent(void) const
@@ -264,7 +271,7 @@
     if (minThermalThreshold >= _maximumSetPoint)
     {
         _maximumSetPoint = minThermalThreshold;
-        _maximumSetPointName = "";
+        _maximumSetPointName = "Minimum";
     }
     else if (_maximumSetPointName.compare(_maximumSetPointNamePrev))
     {
@@ -461,4 +468,20 @@
     return getFailSafeMode();
 }
 
+void DbusPidZone::addPidControlProcess(std::string name, sdbusplus::bus_t& bus,
+                                       std::string objPath, bool defer)
+{
+    _pidsControlProcess[name] = std::make_unique<ProcessObject>(
+        bus, objPath.c_str(),
+        defer ? ProcessObject::action::defer_emit
+              : ProcessObject::action::emit_object_added);
+    // Default enable setting = true
+    _pidsControlProcess[name]->enabled(true);
+}
+
+bool DbusPidZone::isPidProcessEnabled(std::string name)
+{
+    return _pidsControlProcess[name]->enabled();
+}
+
 } // namespace pid_control
diff --git a/pid/zone.hpp b/pid/zone.hpp
index b985f5f..9604c73 100644
--- a/pid/zone.hpp
+++ b/pid/zone.hpp
@@ -11,6 +11,7 @@
 #include <sdbusplus/bus.hpp>
 #include <sdbusplus/server.hpp>
 #include <xyz/openbmc_project/Control/Mode/server.hpp>
+#include <xyz/openbmc_project/Object/Enable/server.hpp>
 
 #include <fstream>
 #include <iostream>
@@ -24,6 +25,9 @@
 using ServerObject = typename sdbusplus::server::object_t<T...>;
 using ModeInterface = sdbusplus::xyz::openbmc_project::Control::server::Mode;
 using ModeObject = ServerObject<ModeInterface>;
+using ProcessInterface =
+    sdbusplus::xyz::openbmc_project::Object::server::Enable;
+using ProcessObject = ServerObject<ProcessInterface>;
 
 namespace pid_control
 {
@@ -98,6 +102,10 @@
     bool manual(bool value) override;
     /* Method for reading whether in fail-safe mode over dbus */
     bool failSafe() const override;
+    /* Method for control process for each loop at runtime */
+    void addPidControlProcess(std::string name, sdbusplus::bus_t& bus,
+                              std::string objPath, bool defer);
+    bool isPidProcessEnabled(std::string name);
 
   private:
     template <bool fanSensorLogging>
@@ -196,6 +204,8 @@
 
     std::vector<std::unique_ptr<Controller>> _fans;
     std::vector<std::unique_ptr<Controller>> _thermals;
+
+    std::map<std::string, std::unique_ptr<ProcessObject>> _pidsControlProcess;
 };
 
 } // namespace pid_control
diff --git a/test/helpers.hpp b/test/helpers.hpp
index 8f81bc6..4d3faea 100644
--- a/test/helpers.hpp
+++ b/test/helpers.hpp
@@ -45,6 +45,7 @@
     EXPECT_CALL(*sdbus_mock,
                 sd_bus_add_object_vtable(IsNull(), NotNull(), StrEq(path),
                                          StrEq(intf), NotNull(), NotNull()))
+        .Times(::testing::AnyNumber())
         .WillOnce(Return(0));
 
     if (!defer)
diff --git a/test/pid_zone_unittest.cpp b/test/pid_zone_unittest.cpp
index 8e74367..4eb3487 100644
--- a/test/pid_zone_unittest.cpp
+++ b/test/pid_zone_unittest.cpp
@@ -26,6 +26,7 @@
 using ::testing::StrEq;
 
 static std::string modeInterface = "xyz.openbmc_project.Control.Mode";
+static std::string enableInterface = "xyz.openbmc_project.Object.Enable";
 
 namespace
 {
@@ -34,10 +35,12 @@
 {
     // Build a PID Zone.
 
-    sdbusplus::SdBusMock sdbus_mock_passive, sdbus_mock_host, sdbus_mock_mode;
+    sdbusplus::SdBusMock sdbus_mock_passive, sdbus_mock_host, sdbus_mock_mode,
+        sdbus_mock_enable;
     auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive);
     auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host);
     auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode);
+    auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
 
     EXPECT_CALL(sdbus_mock_host,
                 sd_bus_add_object_manager(
@@ -58,6 +61,15 @@
     SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface, properties,
                     &d);
 
+    std::string sensorname = "temp1";
+    std::string pidsensorpath = "/xyz/openbmc_project/settings/fanctrl/zone1/" +
+                                sensorname;
+
+    double de;
+    std::vector<std::string> propertiesenable;
+    SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(),
+                    enableInterface, propertiesenable, &de);
+
     DbusPidZone p(zone, minThermalOutput, failSafePercent, cycleTime, m,
                   bus_mock_mode, objPath, defer);
     // Success.
@@ -70,7 +82,7 @@
   protected:
     PidZoneTest() :
         property_index(), properties(), sdbus_mock_passive(), sdbus_mock_host(),
-        sdbus_mock_mode()
+        sdbus_mock_mode(), sdbus_mock_enable()
     {
         EXPECT_CALL(sdbus_mock_host,
                     sd_bus_add_object_manager(
@@ -80,6 +92,7 @@
         auto bus_mock_passive = sdbusplus::get_mocked_new(&sdbus_mock_passive);
         auto bus_mock_host = sdbusplus::get_mocked_new(&sdbus_mock_host);
         auto bus_mock_mode = sdbusplus::get_mocked_new(&sdbus_mock_mode);
+        auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
 
         // Compiler weirdly not happy about just instantiating mgr(...);
         SensorManager m(bus_mock_passive, bus_mock_host);
@@ -88,6 +101,10 @@
         SetupDbusObject(&sdbus_mock_mode, defer, objPath, modeInterface,
                         properties, &property_index);
 
+        SetupDbusObject(&sdbus_mock_enable, defer, pidsensorpath.c_str(),
+                        enableInterface, propertiesenable,
+                        &propertyenable_index);
+
         zone = std::make_unique<DbusPidZone>(zoneId, minThermalOutput,
                                              failSafePercent, cycleTime, mgr,
                                              bus_mock_mode, objPath, defer);
@@ -96,10 +113,13 @@
     // unused
     double property_index;
     std::vector<std::string> properties;
+    double propertyenable_index;
+    std::vector<std::string> propertiesenable;
 
     sdbusplus::SdBusMock sdbus_mock_passive;
     sdbusplus::SdBusMock sdbus_mock_host;
     sdbusplus::SdBusMock sdbus_mock_mode;
+    sdbusplus::SdBusMock sdbus_mock_enable;
     int64_t zoneId = 1;
     double minThermalOutput = 1000.0;
     double failSafePercent = 0.75;
@@ -108,6 +128,10 @@
     SensorManager mgr;
     conf::CycleTime cycleTime;
 
+    std::string sensorname = "temp1";
+    std::string pidsensorpath = "/xyz/openbmc_project/settings/fanctrl/zone1/" +
+                                sensorname;
+
     std::unique_ptr<DbusPidZone> zone;
 };
 
@@ -128,6 +152,28 @@
     EXPECT_TRUE(zone->getManualMode());
 }
 
+TEST_F(PidZoneTest, AddPidControlProcessGetAndSetEnableTest_BehavesAsExpected)
+{
+    // Verifies that the zone starts in enable mode.  Verifies that one can set
+    // enable the mode.
+    auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
+
+    EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
+                                     IsNull(), StrEq(pidsensorpath.c_str()),
+                                     StrEq(enableInterface), NotNull()))
+        .Times(::testing::AnyNumber())
+        .WillOnce(Invoke(
+            [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
+                [[maybe_unused]] const char* interface, const char** names) {
+        EXPECT_STREQ("Enable", names[0]);
+        return 0;
+        }));
+
+    zone->addPidControlProcess(sensorname, bus_mock_enable,
+                               pidsensorpath.c_str(), defer);
+    EXPECT_TRUE(zone->isPidProcessEnabled(sensorname));
+}
+
 TEST_F(PidZoneTest, SetManualMode_RedundantWritesEnabledOnceAfterManualMode)
 {
     // Tests adding a fan PID controller to the zone, and verifies it's
@@ -166,12 +212,31 @@
     // Tests addSetPoint, clearSetPoints, determineMaxSetPointRequest
     // and getMinThermalSetPoint.
 
+    // Need to add pid control process for the zone that can enable
+    // the process and add the set point.
+    auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
+
+    EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
+                                     IsNull(), StrEq(pidsensorpath.c_str()),
+                                     StrEq(enableInterface), NotNull()))
+        .Times(::testing::AnyNumber())
+        .WillOnce(Invoke(
+            [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
+                [[maybe_unused]] const char* interface, const char** names) {
+        EXPECT_STREQ("Enable", names[0]);
+        return 0;
+        }));
+
+    zone->addPidControlProcess(sensorname, bus_mock_enable,
+                               pidsensorpath.c_str(), defer);
+
     // At least one value must be above the minimum thermal setpoint used in
     // the constructor otherwise it'll choose that value
     std::vector<double> values = {100, 200, 300, 400, 500, 5000};
+
     for (auto v : values)
     {
-        zone->addSetPoint(v, "");
+        zone->addSetPoint(v, sensorname);
     }
 
     // This will pull the maximum RPM setpoint request.
@@ -191,10 +256,29 @@
     // Tests adding several RPM setpoints, however, they're all lower than the
     // configured minimal thermal setpoint RPM value.
 
+    // Need to add pid control process for the zone that can enable
+    // the process and add the set point.
+    auto bus_mock_enable = sdbusplus::get_mocked_new(&sdbus_mock_enable);
+
+    EXPECT_CALL(sdbus_mock_mode, sd_bus_emit_properties_changed_strv(
+                                     IsNull(), StrEq(pidsensorpath.c_str()),
+                                     StrEq(enableInterface), NotNull()))
+        .Times(::testing::AnyNumber())
+        .WillOnce(Invoke(
+            [&]([[maybe_unused]] sd_bus* bus, [[maybe_unused]] const char* path,
+                [[maybe_unused]] const char* interface, const char** names) {
+        EXPECT_STREQ("Enable", names[0]);
+        return 0;
+        }));
+
+    zone->addPidControlProcess(sensorname, bus_mock_enable,
+                               pidsensorpath.c_str(), defer);
+
     std::vector<double> values = {100, 200, 300, 400, 500};
+
     for (auto v : values)
     {
-        zone->addSetPoint(v, "");
+        zone->addSetPoint(v, sensorname);
     }
 
     // This will pull the maximum RPM setpoint request.