Conditionally add/remove events action

A new action to add or remove events based on the state of all members
of a group. When all members of a group match the state given, the
events will be enabled. At any time a member's property state no longer
matches, all the events will be removed.

For example, to enable an event that changes the fan floor table using
different values based on the selected state of the current thermal
mode.

- name: set_speed_boundaries_based_on_ambient
  groups:
      - name: zone0_control_mode
        interface: xyz.openbmc_project.Control.ThermalMode
        property:
            name: Current
            type: std::string
  triggers:
      - name: init
        method: getProperties
        handler: setProperty
      - name: signal
        signal: propertiesChanged
        handler: setProperty
  actions:
      - name: use_events_on_state
        property:
            value: DEFAULT
            type: std::string
        events:
            - name: default_fan_speed_boundaries
              groups:
                  - name: zone0_ambient
                    interface: xyz.openbmc_project.Sensor.Value
                    property:
                        name: Value
                        type: int64_t
              triggers:
                  - name: init
                    method: getProperties
                    handler: setProperty
                  - name: signal
                    signal: propertiesChanged
                    handler: setProperty
              actions:
                  - name: set_floor_from_average_sensor_value
                    map:
                        value:
                            - 27000: 3500
                            - 32000: 4600
                            - 37000: 5200
                            - 40000: 5800
                        type: std::map<int64_t, uint64_t>
      - name: use_events_on_state
        property:
            value: CUSTOM
            type: std::string
        events:
            - name: custom_fan_speed_boundaries
              groups:
                  - name: zone0_ambient
                    interface: xyz.openbmc_project.Sensor.Value
                    property:
                        name: Value
                        type: int64_t
              triggers:
                  - name: init
                    method: getProperties
                    handler: setProperty
                  - name: signal
                    signal: propertiesChanged
                    handler: setProperty
              actions:
                  - name: set_floor_from_average_sensor_value
                    map:
                        value:
                            - 27000: 4600
                            - 32000: 5000
                            - 37000: 5400
                            - 40000: 5800
                        type: std::map<int64_t, uint64_t>

Tested:
    Different fan floor events loaded based on thermal mode state

Change-Id: I85a4718e928996d2063e51eb31bfbb45e0e40c0b
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/actions.hpp b/control/actions.hpp
index c30945a..87e9af2 100644
--- a/control/actions.hpp
+++ b/control/actions.hpp
@@ -416,6 +416,71 @@
     };
 }
 
+/**
+ * @brief An action to use a set of events
+ * @details Provides the ability to use a set of events when all members of
+ * a group are at a specified state. When any member of the group no longer
+ * matches the provided state the set of events are removed.
+ *
+ * @param[in] state - State to compare the group's property value to
+ * @param[in] events - The set of events
+ *
+ * @return Lambda function
+ *     A lambda function that checks all group members are at a specified state
+ * and initializes the set of events, otherwise removes them.
+ */
+template <typename T>
+auto use_events_on_state(T&& state,
+                         std::vector<SetSpeedEvent>&& events)
+{
+    return [state = std::forward<T>(state),
+            events = std::move(events)](auto& zone, auto& group)
+    {
+        // Compare all group entries to the state
+        auto useEvents = std::all_of(
+            group.begin(),
+            group.end(),
+            [&zone, &state](auto const& entry)
+            {
+                try
+                {
+                    return zone.template getPropertyValue<T>(
+                            std::get<pathPos>(entry),
+                            std::get<intfPos>(entry),
+                            std::get<propPos>(entry)) == state;
+                }
+                catch (const std::out_of_range& oore)
+                {
+                    // Default to property not equal when not found
+                    return false;
+                }
+            });
+
+        if (useEvents)
+        {
+            // Init events
+            std::for_each(
+                events.begin(),
+                events.end(),
+                [&zone](auto const& entry)
+                {
+                    zone.initEvent(entry);
+                });
+        }
+        else
+        {
+            // Remove events
+            std::for_each(
+                events.begin(),
+                events.end(),
+                [&zone](auto const& entry)
+                {
+                    zone.removeEvent(entry);
+                });
+        }
+    };
+}
+
 } // namespace action
 } // namespace control
 } // namespace fan
diff --git a/control/example/events.yaml b/control/example/events.yaml
index ac33ca0..c5b4188 100644
--- a/control/example/events.yaml
+++ b/control/example/events.yaml
@@ -233,6 +233,13 @@
 #      parameters:
 #          - property
 #          - speed
+#    - name: use_events_on_state
+#      description: >
+#          Use a set of events when all the group properties are at a given
+#          state, otherwise remove the events
+#      parameters:
+#          - property
+#          - events
 #
 #events:
 #    - name: missing_before_high_speed
diff --git a/control/gen-fan-zone-defs.py b/control/gen-fan-zone-defs.py
index 09ff25a..baa0f9b 100755
--- a/control/gen-fan-zone-defs.py
+++ b/control/gen-fan-zone-defs.py
@@ -450,7 +450,7 @@
                                 param += ("make_action(action::" + a['name'])
                             param += "),"
                         param += "}"
-                    elif p == 'defevents' or p == 'altevents':
+                    elif p == 'defevents' or p == 'altevents' or p == 'events':
                         param = "std::vector<SetSpeedEvent>{\n"
                         for i, e in enumerate(eActions[p]):
                             aEvent = getEvent(zNum, zCond, e, events)