Add event init trigger

An event defined with an init trigger will run the event's actions
upon the event initializing. An event initializes when the fan control
application starts or at anytime an event's precondition is valid.

Tested:
    Used missing fan action to set a speed at application start
    Used missing fan action to set a speed upon valid precondition

Change-Id: I96a36425a2ca345371c27d7b580070bec28acaa1
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/control/gen-fan-zone-defs.py b/control/gen-fan-zone-defs.py
index 1ccc420..23c37b9 100755
--- a/control/gen-fan-zone-defs.py
+++ b/control/gen-fan-zone-defs.py
@@ -202,6 +202,26 @@
             e += ")\n"
             e += "\t)),\n"
 
+    if ('init' in event['triggers']):
+        for i in event['triggers']['init']:
+            e += "\tmake_trigger(trigger::init(\n"
+            if ('handler' in i):
+                e += "\t\tmake_handler(\n"
+                if ('type' in i['hparams']) and \
+                    (i['hparams']['type'] is not None):
+                    e += ("handler::" + i['handler'] +
+                        "<" + i['hparams']['type'] + ">(\n")
+                else:
+                    e += "handler::" + i['handler'] + "(\n)"
+                for i, hp in enumerate(i['hparams']['params']):
+                    if (i+1) != len(i['hparams']['params']):
+                        e += i['hparams'][hp] + ",\n"
+                    else:
+                        e += i['hparams'][hp] + "\n"
+                e += ")\n"
+                e += "\t\t)\n"
+            e += "\t)),\n"
+
     e += "},\n"
 
     e += "}"
@@ -262,34 +282,65 @@
     return groups
 
 
-def getHandler(member, group, eHandler, events):
+def getParameters(member, groups, section, events):
     """
-    Extracts and constructs an event handler's parameters
+    Extracts and constructs a section's parameters
     """
-    hparams = {}
-    if ('parameters' in eHandler) and \
-       (eHandler['parameters'] is not None):
-        hplist = []
-        for p in eHandler['parameters']:
-            hp = str(p)
-            if (hp != 'type'):
-                hplist.append(hp)
-                if (hp != 'group'):
-                    hparams[hp] = "\"" + member[hp] + "\""
+    params = {}
+    if ('parameters' in section) and \
+       (section['parameters'] is not None):
+        plist = []
+        for sp in section['parameters']:
+            p = str(sp)
+            if (p != 'type'):
+                plist.append(p)
+                if (p != 'group'):
+                    params[p] = "\"" + member[p] + "\""
                 else:
-                    hparams[hp] = "Group{\n"
-                    for m in group['members']:
-                        hparams[hp] += (
-                            "{\n" +
-                            "\"" + str(m['object']) + "\",\n" +
-                            "\"" + str(m['interface']) + "\"," +
-                            "\"" + str(m['property']) + "\"\n" +
-                            "},\n")
-                    hparams[hp] += "}"
+                    params[p] = "Group\n{\n"
+                    for g in groups:
+                        for m in g['members']:
+                            params[p] += (
+                                "{\"" + str(m['object']) + "\",\n" +
+                                "\"" + str(m['interface']) + "\",\n" +
+                                "\"" + str(m['property']) + "\"},\n")
+                    params[p] += "}"
             else:
-                hparams[hp] = member[hp]
-        hparams['params'] = hplist
-    return hparams
+                params[p] = member[p]
+        params['params'] = plist
+    else:
+        params['params'] = []
+    return params
+
+
+def getInit(eGrps, eTrig, events):
+    """
+    Extracts and constructs an init trigger for the event's groups
+    which are required to be of the same type.
+    """
+    method = {}
+    methods = []
+    # Use the first group member for retrieving the type
+    member = eGrps[0]['members'][0]
+    if ('method' in eTrig) and \
+       (eTrig['method'] is not None):
+        # Add method parameters
+        eMethod = next(m for m in events['methods']
+                       if m['name'] == eTrig['method'])
+        method['method'] = eMethod['name']
+        method['mparams'] = getParameters(
+            member, eGrps, eMethod, events)
+
+        # Add handler parameters
+        eHandler = next(h for h in events['handlers']
+                        if h['name'] == eTrig['handler'])
+        method['handler'] = eHandler['name']
+        method['hparams'] = getParameters(
+            member, eGrps, eHandler, events)
+
+    methods.append(method)
+
+    return methods
 
 
 def getSignal(eGrps, eTrig, events):
@@ -300,36 +351,29 @@
     signals = []
     for group in eGrps:
         for member in group['members']:
-            for eMatches in eTrig['matches']:
-                signal = {}
+            signal = {}
+            # Add signal parameters
+            eSignal = next(s for s in events['signals']
+                           if s['name'] == eTrig['signal'])
+            signal['signal'] = eSignal['name']
+            signal['sparams'] = getParameters(member, eGrps, eSignal, events)
+
+            # If service not given, subscribe to signal match
+            if ('service' not in member):
+                # Add signal match parameters
                 eMatch = next(m for m in events['matches']
-                              if m['name'] == eMatches['name'])
-                # If service not given, subscribe to signal match
-                if ('service' not in member):
-                    signal['match'] = eMatch['name']
-                    params = []
-                    if ('parameters' in eMatch) and \
-                       (eMatch['parameters'] is not None):
-                        for p in eMatch['parameters']:
-                            params.append(member[str(p)])
-                    signal['mparams'] = params
+                              if m['name'] == eSignal['match'])
+                signal['match'] = eMatch['name']
+                signal['mparams'] = getParameters(member, eGrps, eMatch, events)
 
-                if ('parameters' in eMatch['signal']) and \
-                   (eMatch['signal']['parameters'] is not None):
-                    eSignal = eMatch['signal']
-                else:
-                    eSignal = next(s for s in events['signals']
-                                   if s['name'] == eMatch['signal'])
-                signal['signal'] = eSignal['name']
-                signal['sparams'] = getHandler(member, group, eSignal, events)
+            # Add handler parameters
+            eHandler = next(h for h in events['handlers']
+                            if h['name'] == eTrig['handler'])
+            signal['handler'] = eHandler['name']
+            signal['hparams'] = getParameters(member, eGrps, eHandler, events)
 
-                # Add handler parameters
-                eHandler = next(h for h in events['handlers']
-                                if h['name'] == eSignal['handler'])
-                signal['handler'] = eHandler['name']
-                signal['hparams'] = getHandler(member, group, eHandler, events)
+            signals.append(signal)
 
-                signals.append(signal)
     return signals
 
 
@@ -469,6 +513,9 @@
                 event['triggers']['signals'] = []
             triggers = getSignal(event['groups'], trig, events_data)
             event['triggers']['signals'].extend(triggers)
+        elif (trig['name'] == "init"):
+            triggers = getInit(event['groups'], trig, events_data)
+            event['triggers']['init'] = triggers
 
     return event
 
@@ -541,6 +588,9 @@
                 precond['triggers']['pcsigs'] = []
             triggers = getSignal(precond['pcgrps'], trig, events_data)
             precond['triggers']['pcsigs'].extend(triggers)
+        elif (trig['name'] == "init"):
+            triggers = getInit(precond['pcgrps'], trig, events_data)
+            precond['triggers']['init'] = triggers
 
     return precond
 
diff --git a/control/templates/defs.mako b/control/templates/defs.mako
index d574c57..9d66461 100644
--- a/control/templates/defs.mako
+++ b/control/templates/defs.mako
@@ -3,6 +3,23 @@
     return ''.join(4*' '*depth+line for line in str.splitlines(True))
 %>\
 
+<%def name="genParams(par)" buffered="True">
+%if ('type' in par['hparams']) and \
+    (par['hparams']['type'] is not None):
+handler::${par['handler']}<${par['hparams']['type']}>(
+%else:
+handler::${par['handler']}(
+%endif
+%for i, hpk in enumerate(par['hparams']['params']):
+    %if (i+1) != len(par['hparams']['params']):
+    ${par['hparams'][hpk]},
+    %else:
+    ${par['hparams'][hpk]}
+    %endif
+%endfor
+)
+</%def>\
+
 <%def name="genHandler(sig)" buffered="True">
 %if ('type' in sig['sparams']) and \
     (sig['sparams']['type'] is not None):
@@ -13,20 +30,8 @@
 %for spk in sig['sparams']['params']:
 ${sig['sparams'][spk]},
 %endfor
-%if ('type' in sig['hparams']) and \
-    (sig['hparams']['type'] is not None):
-handler::${sig['handler']}<${sig['hparams']['type']}>(
-%else:
-handler::${sig['handler']}(
-%endif
-%for i, hpk in enumerate(sig['hparams']['params']):
-    %if (i+1) != len(sig['hparams']['params']):
-    ${sig['hparams'][hpk]},
-    %else:
-    ${sig['hparams'][hpk]}
-    %endif
-%endfor
-))
+${genParams(par=sig)}\
+)
 </%def>\
 
 <%def name="genSSE(event)" buffered="True">
@@ -90,5 +95,16 @@
     )),
     %endfor
     %endif
+    %if ('init' in event['triggers']):
+    %for i in event['triggers']['init']:
+    make_trigger(trigger::init(
+        %if ('handler' in i):
+        make_handler(\
+        ${indent(genParams(par=i), 3)}\
+        )
+        %endif
+    )),
+    %endfor
+    %endif
 },
 </%def>\
diff --git a/control/templates/fan_zone_defs.mako.cpp b/control/templates/fan_zone_defs.mako.cpp
index 5eadc6d..4b34c46 100644
--- a/control/templates/fan_zone_defs.mako.cpp
+++ b/control/templates/fan_zone_defs.mako.cpp
@@ -188,6 +188,17 @@
                             )),
                             %endfor
                             %endif
+                            %if ('init' in event['pc']['triggers']):
+                            %for i in event['pc']['triggers']['init']:
+                            make_trigger(trigger::init(
+                                %if ('handler' in i):
+                                make_handler(\
+                                ${indent(genParams(par=i), 3)}\
+                                )
+                                %endif
+                            )),
+                            %endfor
+                            %endif
                         },
                     %endif
                     },
diff --git a/control/triggers.cpp b/control/triggers.cpp
index 376ac9b..3cddce6 100644
--- a/control/triggers.cpp
+++ b/control/triggers.cpp
@@ -77,6 +77,31 @@
     };
 }
 
+Trigger init(Handler&& handler)
+{
+    return [handler = std::move(handler)](control::Zone& zone,
+                                          const Group& group,
+                                          const std::vector<Action>& actions)
+    {
+        // A handler function is optional
+        if (handler)
+        {
+            sdbusplus::message::message nullMsg{nullptr};
+            // Execute the given handler function prior to running the actions
+            handler(zone.getBus(), nullMsg, zone);
+        }
+        // Run action functions for initial event state
+        std::for_each(
+            actions.begin(),
+            actions.end(),
+            [&zone, &group](auto const& action)
+            {
+                action(zone, group);
+            }
+        );
+    };
+}
+
 } // namespace trigger
 } // namespace control
 } // namespace fan
diff --git a/control/triggers.hpp b/control/triggers.hpp
index aad64d4..35c3091 100644
--- a/control/triggers.hpp
+++ b/control/triggers.hpp
@@ -36,6 +36,19 @@
  */
 Trigger signal(const std::string& match, Handler&& handler);
 
+/**
+ * @brief A trigger for actions to run at event init
+ * @details Runs the event actions when the event is initialized. An event
+ * is initialized at application start or each time an event's precondition
+ * transitions to a valid state.
+ *
+ * @param[in] handler - Handler function to use for event init
+ *
+ * @return Trigger lambda function
+ *     A Trigger function that runs actions at event init
+ */
+Trigger init(Handler&& handler = nullptr);
+
 } // namespace trigger
 } // namespace control
 } // namespace fan
diff --git a/control/zone.cpp b/control/zone.cpp
index 944da09..fc0f6f4 100644
--- a/control/zone.cpp
+++ b/control/zone.cpp
@@ -330,16 +330,6 @@
                     std::get<actionsPos>(event));
         }
     );
-
-    // Run action functions for initial event state
-    std::for_each(
-        std::get<actionsPos>(event).begin(),
-        std::get<actionsPos>(event).end(),
-        [this, &event](auto const& action)
-        {
-            action(*this,
-                   std::get<groupPos>(event));
-        });
 }
 
 void Zone::removeEvent(const SetSpeedEvent& event)