Support speed decrease events

A net decrease speed action determines the net decrease delta from each
group member's property value and requests a speed decrease of that
delta from the current target speed.

From all the requests for a speed decrease on a zone, only the lowest
net decrease is used from all the groups subscribed to decrease a zone's
speed. The new target speed is only decreased when no increase requests
exist and the resulting target is above the zone's floor speed,
otherwise the floor speed is set as the new target.

Resolves openbmc/openbmc#1626

Change-Id: Iaefa7b25c3f44691dd3ca4084bfddd3c1a504de9
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/actions.hpp b/control/actions.hpp
index d4dedba..325cbbd 100644
--- a/control/actions.hpp
+++ b/control/actions.hpp
@@ -255,6 +255,58 @@
     };
 }
 
+/**
+ * @brief An action to set the speed decrease delta and request speed change
+ * @details Provides the ability to determine what the net decrease delta each
+ * zone's fan speeds should be updated by from their current target speed, and
+ * request that speed change occur on the next decrease interval.
+ *
+ * @param[in] state - State to compare the group's property value to
+ * @param[in] speedDelta - Speed delta of the group
+ *
+ * @return Lambda function
+ *     A lambda function that determines the net decrease delta and requests
+ * a new target speed with that decrease for the zone.
+ */
+template <typename T>
+auto set_net_decrease_speed(T&& state, uint64_t speedDelta)
+{
+    return [speedDelta,
+            state = std::forward<T>(state)](auto& zone, auto& group)
+    {
+        auto netDelta = zone.getDecSpeedDelta();
+        std::for_each(
+            group.begin(),
+            group.end(),
+            [&zone, &state, &speedDelta, &netDelta](auto const& entry)
+            {
+                T value = zone.template getPropertyValue<T>(
+                        entry.first,
+                        std::get<intfPos>(entry.second),
+                        std::get<propPos>(entry.second));
+                // TODO openbmc/phosphor-fan-presence#7 - Support possible
+                // state types for comparison
+                if (value < state)
+                {
+                    if (netDelta == 0)
+                    {
+                        netDelta = (state - value) * speedDelta;
+                    }
+                    else
+                    {
+                        // Decrease is the difference times
+                        // the given speed delta
+                        netDelta = std::min(netDelta,
+                                            (state - value) * speedDelta);
+                    }
+                }
+            }
+        );
+        // Request speed decrease to occur on decrease interval
+        zone.requestSpeedDecrease(netDelta);
+    };
+}
+
 } // namespace action
 } // namespace control
 } // namespace fan