control: Add setFloorHold to Zone

Similar to the existing Zone::setTargetHold function, setFloorHold
allows an action to set a floor and hold it, meaning the fans are not
allowed to decrease to a value below that floor until the hold is
released.  Adding this allows multiple actions to set the floor, without
worrying if one is overwriting another.

It also adds some flight recorder entries with the ID zone-floorX when
floor holds are added and removed.  An example of that output is:

zone-floor0: count_state_floor-1 is setting floor hold to 7777
zone-floor0: Setting new floor to 7777
zone-floor0: mapped_floor-0 is setting floor hold to 10700
zone-floor0: Setting new floor to 10700
zone-floor0: count_state_floor-1 is removing floor hold

Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I1756494e61ada60d44e58704431fb882ef7646a4
diff --git a/control/json/zone.cpp b/control/json/zone.cpp
index 6701c83..5cc0c35 100644
--- a/control/json/zone.cpp
+++ b/control/json/zone.cpp
@@ -15,6 +15,7 @@
  */
 #include "zone.hpp"
 
+#include "../utils/flight_recorder.hpp"
 #include "dbus_zone.hpp"
 #include "fan.hpp"
 #include "sdbusplus.hpp"
@@ -147,19 +148,19 @@
 {
     if (!hold)
     {
-        _holds.erase(ident);
+        _targetHolds.erase(ident);
     }
     else
     {
-        _holds[ident] = target;
+        _targetHolds[ident] = target;
         _isActive = false;
     }
 
-    auto itHoldMax = std::max_element(_holds.begin(), _holds.end(),
+    auto itHoldMax = std::max_element(_targetHolds.begin(), _targetHolds.end(),
                                       [](const auto& aHold, const auto& bHold) {
                                           return aHold.second < bHold.second;
                                       });
-    if (itHoldMax == _holds.end())
+    if (itHoldMax == _targetHolds.end())
     {
         _isActive = true;
     }
@@ -173,6 +174,71 @@
     }
 }
 
+void Zone::setFloorHold(const std::string& ident, uint64_t target, bool hold)
+{
+    using namespace std::string_literals;
+
+    if (!hold)
+    {
+        size_t removed = _floorHolds.erase(ident);
+        if (removed)
+        {
+            FlightRecorder::instance().log(
+                "zone-floor"s + getName(),
+                fmt::format("{} is removing floor hold", ident));
+        }
+    }
+    else
+    {
+        if (!((_floorHolds.find(ident) != _floorHolds.end()) &&
+              (_floorHolds[ident] == target)))
+        {
+            FlightRecorder::instance().log(
+                "zone-floor"s + getName(),
+                fmt::format("{} is setting floor hold to {}", ident, target));
+        }
+        _floorHolds[ident] = target;
+    }
+
+    if (!std::all_of(_floorChange.begin(), _floorChange.end(),
+                     [](const auto& entry) { return entry.second; }))
+    {
+        return;
+    }
+
+    auto itHoldMax = std::max_element(_floorHolds.begin(), _floorHolds.end(),
+                                      [](const auto& aHold, const auto& bHold) {
+                                          return aHold.second < bHold.second;
+                                      });
+    if (itHoldMax == _floorHolds.end())
+    {
+        if (_floor != _defaultFloor)
+        {
+            FlightRecorder::instance().log(
+                "zone-floor"s + getName(),
+                fmt::format("No set floor exists, using default floor",
+                            _defaultFloor));
+        }
+        _floor = _defaultFloor;
+    }
+    else
+    {
+        if (_floor != itHoldMax->second)
+        {
+            FlightRecorder::instance().log(
+                "zone-floor"s + getName(),
+                fmt::format("Setting new floor to {}", itHoldMax->second));
+        }
+        _floor = itHoldMax->second;
+    }
+
+    // Floor above target, update target to floor
+    if (_target < _floor)
+    {
+        requestIncrease(_floor - _target);
+    }
+}
+
 void Zone::setFloor(uint64_t target)
 {
     // Check all entries are set to allow floor to be set
@@ -378,7 +444,8 @@
     output["floor_change"] = _floorChange;
     output["decrease_allowed"] = _decAllowed;
     output["persisted_props"] = _propsPersisted;
-    output["holds"] = _holds;
+    output["target_holds"] = _targetHolds;
+    output["floor_holds"] = _floorHolds;
 
     return output;
 }