1#!/usr/bin/env python
2
3"""
4This script reads in fan definition and zone definition YAML
5files and generates a set of structures for use by the fan control code.
6"""
7
8import os
9import sys
10import yaml
11from argparse import ArgumentParser
12from mako.template import Template
13
14tmpl = '''\
15<%!
16def indent(str, depth):
17    return ''.join(4*' '*depth+line for line in str.splitlines(True))
18%>\
19<%def name="genHandler(sig)" buffered="True">
20%if ('type' in sig['sparams']) and \
21    (sig['sparams']['type'] is not None):
22${sig['signal']}<${sig['sparams']['type']}>(
23%else:
24${sig['signal']}(
25%endif
26%for spk in sig['sparams']['params']:
27${sig['sparams'][spk]},
28%endfor
29%if ('type' in sig['hparams']) and \
30    (sig['hparams']['type'] is not None):
31handler::${sig['handler']}<${sig['hparams']['type']}>(
32%else:
33handler::${sig['handler']}(
34%endif
35%for i, hpk in enumerate(sig['hparams']['params']):
36    %if (i+1) != len(sig['hparams']['params']):
37    ${sig['hparams'][hpk]},
38    %else:
39    ${sig['hparams'][hpk]}
40    %endif
41%endfor
42))
43</%def>\
44<%def name="genSSE(event)" buffered="True">
45Group{
46%for member in event['group']:
47{
48    "${member['object']}",
49    {"${member['interface']}",
50     "${member['property']}"}
51},
52%endfor
53},
54std::vector<Action>{
55%for a in event['action']:
56%if len(a['parameters']) != 0:
57make_action(action::${a['name']}(
58%else:
59make_action(action::${a['name']}
60%endif
61%for i, p in enumerate(a['parameters']):
62%if (i+1) != len(a['parameters']):
63    ${p},
64%else:
65    ${p})
66%endif
67%endfor
68),
69%endfor
70},
71Timer{
72    ${event['timer']['interval']},
73    ${event['timer']['type']}
74},
75std::vector<Signal>{
76%for s in event['signals']:
77    Signal{
78        match::${s['match']}(
79        %for i, mp in enumerate(s['mparams']):
80        %if (i+1) != len(s['mparams']):
81        "${mp}",
82        %else:
83        "${mp}"
84        %endif
85        %endfor
86        ),
87        make_handler(\
88        ${indent(genHandler(sig=s), 3)}\
89        )
90    },
91%endfor
92}
93</%def>\
94/* This is a generated file. */
95#include "manager.hpp"
96#include "functor.hpp"
97#include "actions.hpp"
98#include "handlers.hpp"
99#include "preconditions.hpp"
100#include "matches.hpp"
101
102using namespace phosphor::fan::control;
103
104const unsigned int Manager::_powerOnDelay{${mgr_data['power_on_delay']}};
105
106const std::vector<ZoneGroup> Manager::_zoneLayouts
107{
108%for zone_group in zones:
109    ZoneGroup{
110        std::vector<Condition>{
111        %for condition in zone_group['conditions']:
112            Condition{
113                "${condition['type']}",
114                std::vector<ConditionProperty>{
115                %for property in condition['properties']:
116                    ConditionProperty{
117                        "${property['property']}",
118                        "${property['interface']}",
119                        "${property['path']}",
120                        static_cast<${property['type']}>(${property['value']}),
121                    },
122                    %endfor
123                },
124            },
125            %endfor
126        },
127        std::vector<ZoneDefinition>{
128        %for zone in zone_group['zones']:
129            ZoneDefinition{
130                ${zone['num']},
131                ${zone['full_speed']},
132                ${zone['default_floor']},
133                ${zone['increase_delay']},
134                ${zone['decrease_interval']},
135                std::vector<FanDefinition>{
136                %for fan in zone['fans']:
137                    FanDefinition{
138                        "${fan['name']}",
139                        std::vector<std::string>{
140                        %for sensor in fan['sensors']:
141                            "${sensor}",
142                        %endfor
143                        },
144                        "${fan['target_interface']}"
145                    },
146                %endfor
147                },
148                std::vector<SetSpeedEvent>{
149                %for event in zone['events']:
150                    %if ('pc' in event) and \
151                        (event['pc'] is not None):
152                    SetSpeedEvent{
153                        Group{
154                        %for member in event['pc']['pcgrp']:
155                        {
156                            "${member['object']}",
157                            {"${member['interface']}",
158                             "${member['property']}"}
159                        },
160                        %endfor
161                        },
162                        std::vector<Action>{
163                        %for i, a in enumerate(event['pc']['pcact']):
164                        %if len(a['params']) != 0:
165                        make_action(
166                            precondition::${a['name']}(
167                        %else:
168                        make_action(
169                            precondition::${a['name']}
170                        %endif
171                        %for p in a['params']:
172                        ${p['type']}${p['open']}
173                        %for j, v in enumerate(p['values']):
174                        %if (j+1) != len(p['values']):
175                            ${v['value']},
176                        %else:
177                            ${v['value']}
178                        %endif
179                        %endfor
180                        ${p['close']},
181                        %endfor
182                        %if (i+1) != len(event['pc']['pcact']):
183                        %if len(a['params']) != 0:
184                        )),
185                        %else:
186                        ),
187                        %endif
188                        %endif
189                        %endfor
190                    std::vector<SetSpeedEvent>{
191                    %for pcevt in event['pc']['pcevts']:
192                    SetSpeedEvent{\
193                    ${indent(genSSE(event=pcevt), 6)}\
194                    },
195                    %endfor
196                    %else:
197                    SetSpeedEvent{\
198                    ${indent(genSSE(event=event), 6)}
199                    %endif
200                    %if ('pc' in event) and (event['pc'] is not None):
201                    }
202                        %if len(event['pc']['pcact'][-1]['params']) != 0:
203                        )),
204                        %else:
205                        ),
206                        %endif
207                        },
208                        Timer{
209                            ${event['pc']['pctime']['interval']},
210                            ${event['pc']['pctime']['type']}
211                        },
212                        std::vector<Signal>{
213                        %for s in event['pc']['pcsigs']:
214                            Signal{
215                                match::${s['match']}(
216                                %for i, mp in enumerate(s['mparams']):
217                                %if (i+1) != len(s['mparams']):
218                                "${mp}",
219                                %else:
220                                "${mp}"
221                                %endif
222                                %endfor
223                                ),
224                                make_handler(\
225                                ${indent(genHandler(sig=s), 9)}\
226                                )
227                            },
228                        %endfor
229                        }
230                    %endif
231                    },
232                %endfor
233                }
234            },
235        %endfor
236        }
237    },
238%endfor
239};
240'''
241
242
243def convertToMap(listOfDict):
244    """
245    Converts a list of dictionary entries to a std::map initialization list.
246    """
247    listOfDict = listOfDict.replace('\'', '\"')
248    listOfDict = listOfDict.replace('[', '{')
249    listOfDict = listOfDict.replace(']', '}')
250    listOfDict = listOfDict.replace(':', ',')
251    return listOfDict
252
253
254def getActions(edata, actions, events):
255    """
256    Extracts and constructs the make_action function call for
257    all the actions within the given event.
258    """
259    action = []
260    for eActions in actions['actions']:
261        actions = {}
262        eAction = next(a for a in events['actions']
263                       if a['name'] == eActions['name'])
264        actions['name'] = eAction['name']
265        params = []
266        if ('parameters' in eAction) and \
267           (eAction['parameters'] is not None):
268            for p in eAction['parameters']:
269                param = "static_cast<"
270                if type(eActions[p]) is not dict:
271                    if p == 'actions':
272                        param = "std::vector<Action>{"
273                        pActs = getActions(edata, eActions, events)
274                        for a in pActs:
275                            if (len(a['parameters']) != 0):
276                                param += (
277                                    "make_action(action::" +
278                                    a['name'] +
279                                    "(\n")
280                                for i, ap in enumerate(a['parameters']):
281                                    if (i+1) != len(a['parameters']):
282                                        param += (ap + ",")
283                                    else:
284                                        param += (ap + ")")
285                            else:
286                                param += ("make_action(action::" + a['name'])
287                            param += "),"
288                        param += "}"
289                    elif p == 'property':
290                        if isinstance(eActions[p], str) or \
291                           "string" in str(edata['property']['type']).lower():
292                            param += (
293                                str(edata['property']['type']).lower() +
294                                ">(\"" + str(eActions[p]) + "\")")
295                        else:
296                            param += (
297                                str(edata['property']['type']).lower() +
298                                ">(" + str(eActions[p]).lower() + ")")
299                    else:
300                        # Default type to 'size_t' when not given
301                        param += ("size_t>(" + str(eActions[p]).lower() + ")")
302                else:
303                    if p == 'timer':
304                        param = (
305                            "Timer{static_cast<std::chrono::seconds>(" +
306                            str(eActions[p]['delay']) + "), " +
307                            "util::Timer::TimerType::" +
308                            str(eActions[p]['type']) + "}")
309                    else:
310                        param += (str(eActions[p]['type']).lower() + ">(")
311                        if p != 'map':
312                            if isinstance(eActions[p]['value'], str) or \
313                               "string" in str(eActions[p]['type']).lower():
314                                param += \
315                                    "\"" + str(eActions[p]['value']) + "\")"
316                            else:
317                                param += \
318                                    str(eActions[p]['value']).lower() + ")"
319                        else:
320                            param += (
321                                str(eActions[p]['type']).lower() +
322                                convertToMap(str(eActions[p]['value'])) + ")")
323                params.append(param)
324        actions['parameters'] = params
325        action.append(actions)
326    return action
327
328
329def getEvent(zone_num, zone_conditions, e, events_data):
330    """
331    Parses the sections of an event and populates the properties
332    that construct an event within the generated source.
333    """
334    event = {}
335    # Zone numbers are optional in the events yaml but skip if this
336    # zone's zone number is not in the event's zone numbers
337    if all('zones' in z and
338           z['zones'] is not None and
339           zone_num not in z['zones']
340           for z in e['zone_conditions']):
341        return
342
343    # Zone conditions are optional in the events yaml but skip
344    # if this event's condition is not in this zone's conditions
345    if all('name' in z and z['name'] is not None and
346           not any(c['name'] == z['name'] for c in zone_conditions)
347           for z in e['zone_conditions']):
348        return
349
350    # Add set speed event group
351    group = []
352    groups = next(g for g in events_data['groups']
353                  if g['name'] == e['group'])
354    for member in groups['members']:
355        members = {}
356        members['object'] = (groups['type'] +
357                             member)
358        members['interface'] = e['interface']
359        members['property'] = e['property']['name']
360        members['type'] = e['property']['type']
361        group.append(members)
362    event['group'] = group
363
364    # Add set speed actions and function parameters
365    event['action'] = getActions(e, e, events_data)
366
367    # Add signal handlers
368    signals = []
369    for member in group:
370        for eMatches in e['matches']:
371            signal = {}
372            eMatch = next(m for m in events_data['matches']
373                          if m['name'] == eMatches['name'])
374            signal['match'] = eMatch['name']
375            params = []
376            if ('parameters' in eMatch) and \
377               (eMatch['parameters'] is not None):
378                for p in eMatch['parameters']:
379                    params.append(member[str(p)])
380            signal['mparams'] = params
381            eSignal = next(s for s in events_data['signals']
382                           if s['name'] == eMatch['signal'])
383            signal['signal'] = eSignal['name']
384            sparams = {}
385            if ('parameters' in eSignal) and \
386               (eSignal['parameters'] is not None):
387                splist = []
388                for p in eSignal['parameters']:
389                    sp = str(p)
390                    if (sp != 'type'):
391                        splist.append(sp)
392                        if (sp != 'group'):
393                            sparams[sp] = "\"" + member[sp] + "\""
394                        else:
395                            sparams[sp] = "Group{\n"
396                            for m in group:
397                                sparams[sp] += (
398                                    "{\n" +
399                                    "\"" + str(m['object']) + "\",\n" +
400                                    "{\"" + str(m['interface']) + "\"," +
401                                    "\"" + str(m['property']) + "\"}\n" +
402                                    "},\n")
403                            sparams[sp] += "}"
404                    else:
405                        sparams[sp] = member[sp]
406                sparams['params'] = splist
407            signal['sparams'] = sparams
408            # Add signal handler
409            eHandler = next(h for h in events_data['handlers']
410                            if h['name'] == eSignal['handler'])
411            signal['handler'] = eHandler['name']
412            hparams = {}
413            if ('parameters' in eHandler) and \
414               (eHandler['parameters'] is not None):
415                hplist = []
416                for p in eHandler['parameters']:
417                    hp = str(p)
418                    if (hp != 'type'):
419                        hplist.append(hp)
420                        if (hp != 'group'):
421                            hparams[hp] = "\"" + member[hp] + "\""
422                        else:
423                            hparams[hp] = "Group{\n"
424                            for m in group:
425                                hparams[hp] += (
426                                    "{\n" +
427                                    "\"" + str(m['object']) + "\",\n" +
428                                    "{\"" + str(m['interface']) + "\"," +
429                                    "\"" + str(m['property']) + "\"}\n" +
430                                    "},\n")
431                            hparams[hp] += "}"
432                    else:
433                        hparams[hp] = member[hp]
434                hparams['params'] = hplist
435            signal['hparams'] = hparams
436            signals.append(signal)
437    event['signals'] = signals
438
439    # Add optional action call timer
440    timer = {}
441    interval = "static_cast<std::chrono::seconds>"
442    if ('timer' in e) and \
443       (e['timer'] is not None):
444        timer['interval'] = (interval +
445                             "(" +
446                             str(e['timer']['interval']) +
447                             ")")
448    else:
449        timer['interval'] = (interval +
450                             "(" + str(0) + ")")
451    timer['type'] = "util::Timer::TimerType::repeating"
452    event['timer'] = timer
453
454    return event
455
456
457def addPrecondition(zNum, zCond, event, events_data):
458    """
459    Parses the precondition section of an event and populates the necessary
460    structures to generate a precondition for a set speed event.
461    """
462    precond = {}
463    # Add set speed event precondition group
464    group = []
465    for grp in event['precondition']['groups']:
466        groups = next(g for g in events_data['groups']
467                      if g['name'] == grp['name'])
468        for member in groups['members']:
469            members = {}
470            members['object'] = (groups['type'] +
471                                 member)
472            members['interface'] = grp['interface']
473            members['property'] = grp['property']['name']
474            members['type'] = grp['property']['type']
475            if isinstance(grp['property']['value'], str) or \
476               "string" in str(members['type']).lower():
477                members['value'] = "\"" + grp['property']['value'] + "\""
478            else:
479                members['value'] = grp['property']['value']
480            group.append(members)
481    precond['pcgrp'] = group
482
483    # Add set speed event precondition actions
484    pc = []
485    pcs = {}
486    pcs['name'] = event['precondition']['name']
487    epc = next(p for p in events_data['preconditions']
488               if p['name'] == event['precondition']['name'])
489    params = []
490    for p in epc['parameters']:
491        param = {}
492        if p == 'groups':
493            param['type'] = "std::vector<PrecondGroup>"
494            param['open'] = "{"
495            param['close'] = "}"
496            values = []
497            for pcgrp in group:
498                value = {}
499                value['value'] = (
500                    "PrecondGroup{\"" +
501                    str(pcgrp['object']) + "\",\"" +
502                    str(pcgrp['interface']) + "\",\"" +
503                    str(pcgrp['property']) + "\"," +
504                    "static_cast<" +
505                    str(pcgrp['type']).lower() + ">")
506                if isinstance(pcgrp['value'], str) or \
507                   "string" in str(pcgrp['type']).lower():
508                    value['value'] += ("(" + str(pcgrp['value']) + ")}")
509                else:
510                    value['value'] += \
511                        ("(" + str(pcgrp['value']).lower() + ")}")
512                values.append(value)
513            param['values'] = values
514        params.append(param)
515    pcs['params'] = params
516    pc.append(pcs)
517    precond['pcact'] = pc
518
519    pcevents = []
520    for pce in event['precondition']['events']:
521        pcevent = getEvent(zNum, zCond, pce, events_data)
522        if not pcevent:
523            continue
524        pcevents.append(pcevent)
525    precond['pcevts'] = pcevents
526
527    # Add precondition signal handlers
528    signals = []
529    for member in group:
530        for eMatches in event['precondition']['matches']:
531            signal = {}
532            eMatch = next(m for m in events_data['matches']
533                          if m['name'] == eMatches['name'])
534            signal['match'] = eMatch['name']
535            params = []
536            if ('parameters' in eMatch) and \
537               (eMatch['parameters'] is not None):
538                for p in eMatch['parameters']:
539                    params.append(member[str(p)])
540            signal['mparams'] = params
541            eSignal = next(s for s in events_data['signals']
542                           if s['name'] == eMatch['signal'])
543            signal['signal'] = eSignal['name']
544            sparams = {}
545            if ('parameters' in eSignal) and \
546               (eSignal['parameters'] is not None):
547                splist = []
548                for p in eSignal['parameters']:
549                    sp = str(p)
550                    if (sp != 'type'):
551                        splist.append(sp)
552                        if (sp != 'group'):
553                            sparams[sp] = "\"" + member[sp] + "\""
554                        else:
555                            sparams[sp] = "Group{\n"
556                            for m in group:
557                                sparams[sp] += (
558                                    "{\n" +
559                                    "\"" + str(m['object']) + "\",\n" +
560                                    "{\"" + str(m['interface']) + "\"," +
561                                    "\"" + str(m['property']) + "\"}\n" +
562                                    "},\n")
563                            sparams[sp] += "}"
564                    else:
565                        sparams[sp] = member[sp]
566                sparams['params'] = splist
567            signal['sparams'] = sparams
568            # Add signal handler
569            eHandler = next(h for h in events_data['handlers']
570                            if h['name'] == eSignal['handler'])
571            signal['handler'] = eHandler['name']
572            hparams = {}
573            if ('parameters' in eHandler) and \
574               (eHandler['parameters'] is not None):
575                hplist = []
576                for p in eHandler['parameters']:
577                    hp = str(p)
578                    if (hp != 'type'):
579                        hplist.append(hp)
580                        if (hp != 'group'):
581                            hparams[hp] = "\"" + member[hp] + "\""
582                        else:
583                            hparams[hp] = "Group{\n"
584                            for m in group:
585                                hparams[hp] += (
586                                    "{\n" +
587                                    "\"" + str(m['object']) + "\",\n" +
588                                    "{\"" + str(m['interface']) + "\"," +
589                                    "\"" + str(m['property']) + "\"}\n" +
590                                    "},\n")
591                            hparams[hp] += "}"
592                    else:
593                        hparams[hp] = member[hp]
594                hparams['params'] = hplist
595            signal['hparams'] = hparams
596            signals.append(signal)
597    precond['pcsigs'] = signals
598
599    # Add optional action call timer
600    timer = {}
601    interval = "static_cast<std::chrono::seconds>"
602    if ('timer' in event['precondition']) and \
603       (event['precondition']['timer'] is not None):
604        timer['interval'] = (interval +
605                             "(" +
606                             str(event['precondition']['timer']['interval']) +
607                             ")")
608    else:
609        timer['interval'] = (interval +
610                             "(" + str(0) + ")")
611    timer['type'] = "util::Timer::TimerType::repeating"
612    precond['pctime'] = timer
613
614    return precond
615
616
617def getEventsInZone(zone_num, zone_conditions, events_data):
618    """
619    Constructs the event entries defined for each zone using the events yaml
620    provided.
621    """
622    events = []
623
624    if 'events' in events_data:
625        for e in events_data['events']:
626            event = {}
627            # Add precondition if given
628            if ('precondition' in e) and \
629               (e['precondition'] is not None):
630                event['pc'] = addPrecondition(zone_num,
631                                              zone_conditions,
632                                              e,
633                                              events_data)
634            else:
635                event = getEvent(zone_num, zone_conditions, e, events_data)
636                if not event:
637                    continue
638            events.append(event)
639
640    return events
641
642
643def getFansInZone(zone_num, profiles, fan_data):
644    """
645    Parses the fan definition YAML files to find the fans
646    that match both the zone passed in and one of the
647    cooling profiles.
648    """
649
650    fans = []
651
652    for f in fan_data['fans']:
653
654        if zone_num != f['cooling_zone']:
655            continue
656
657        # 'cooling_profile' is optional (use 'all' instead)
658        if f.get('cooling_profile') is None:
659            profile = "all"
660        else:
661            profile = f['cooling_profile']
662
663        if profile not in profiles:
664            continue
665
666        fan = {}
667        fan['name'] = f['inventory']
668        fan['sensors'] = f['sensors']
669        fan['target_interface'] = f.get(
670            'target_interface',
671            'xyz.openbmc_project.Control.FanSpeed')
672        fans.append(fan)
673
674    return fans
675
676
677def getConditionInZoneConditions(zone_condition, zone_conditions_data):
678    """
679    Parses the zone conditions definition YAML files to find the condition
680    that match both the zone condition passed in.
681    """
682
683    condition = {}
684
685    for c in zone_conditions_data['conditions']:
686
687        if zone_condition != c['name']:
688            continue
689        condition['type'] = c['type']
690        properties = []
691        for p in c['properties']:
692            property = {}
693            property['property'] = p['property']
694            property['interface'] = p['interface']
695            property['path'] = p['path']
696            property['type'] = p['type'].lower()
697            property['value'] = str(p['value']).lower()
698            properties.append(property)
699        condition['properties'] = properties
700
701        return condition
702
703
704def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
705    """
706    Combines the zone definition YAML and fan
707    definition YAML to create a data structure defining
708    the fan cooling zones.
709    """
710
711    zone_groups = []
712
713    for group in zone_data:
714        conditions = []
715        # zone conditions are optional
716        if 'zone_conditions' in group and group['zone_conditions'] is not None:
717            for c in group['zone_conditions']:
718
719                if not zone_conditions_data:
720                    sys.exit("No zone_conditions YAML file but " +
721                             "zone_conditions used in zone YAML")
722
723                condition = getConditionInZoneConditions(c['name'],
724                                                         zone_conditions_data)
725
726                if not condition:
727                    sys.exit("Missing zone condition " + c['name'])
728
729                conditions.append(condition)
730
731        zone_group = {}
732        zone_group['conditions'] = conditions
733
734        zones = []
735        for z in group['zones']:
736            zone = {}
737
738            # 'zone' is required
739            if ('zone' not in z) or (z['zone'] is None):
740                sys.exit("Missing fan zone number in " + zone_yaml)
741
742            zone['num'] = z['zone']
743
744            zone['full_speed'] = z['full_speed']
745
746            zone['default_floor'] = z['default_floor']
747
748            # 'increase_delay' is optional (use 0 by default)
749            key = 'increase_delay'
750            zone[key] = z.setdefault(key, 0)
751
752            # 'decrease_interval' is optional (use 0 by default)
753            key = 'decrease_interval'
754            zone[key] = z.setdefault(key, 0)
755
756            # 'cooling_profiles' is optional (use 'all' instead)
757            if ('cooling_profiles' not in z) or \
758                    (z['cooling_profiles'] is None):
759                profiles = ["all"]
760            else:
761                profiles = z['cooling_profiles']
762
763            fans = getFansInZone(z['zone'], profiles, fan_data)
764            events = getEventsInZone(z['zone'], group['zone_conditions'],
765                                     events_data)
766
767            if len(fans) == 0:
768                sys.exit("Didn't find any fans in zone " + str(zone['num']))
769
770            zone['fans'] = fans
771            zone['events'] = events
772            zones.append(zone)
773
774        zone_group['zones'] = zones
775        zone_groups.append(zone_group)
776
777    return zone_groups
778
779
780if __name__ == '__main__':
781    parser = ArgumentParser(
782        description="Phosphor fan zone definition parser")
783
784    parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
785                        default="example/zones.yaml",
786                        help='fan zone definitional yaml')
787    parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
788                        default="example/fans.yaml",
789                        help='fan definitional yaml')
790    parser.add_argument('-e', '--events_yaml', dest='events_yaml',
791                        help='events to set speeds yaml')
792    parser.add_argument('-c', '--zone_conditions_yaml',
793                        dest='zone_conditions_yaml',
794                        help='conditions to determine zone yaml')
795    parser.add_argument('-o', '--output_dir', dest='output_dir',
796                        default=".",
797                        help='output directory')
798    args = parser.parse_args()
799
800    if not args.zone_yaml or not args.fan_yaml:
801        parser.print_usage()
802        sys.exit(-1)
803
804    with open(args.zone_yaml, 'r') as zone_input:
805        zone_data = yaml.safe_load(zone_input) or {}
806
807    with open(args.fan_yaml, 'r') as fan_input:
808        fan_data = yaml.safe_load(fan_input) or {}
809
810    events_data = {}
811    if args.events_yaml:
812        with open(args.events_yaml, 'r') as events_input:
813            events_data = yaml.safe_load(events_input) or {}
814
815    zone_conditions_data = {}
816    if args.zone_conditions_yaml:
817        with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
818            zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
819
820    zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
821                                fan_data, events_data, zone_conditions_data)
822
823    manager_config = zone_data.get('manager_configuration', {})
824
825    if manager_config.get('power_on_delay') is None:
826        manager_config['power_on_delay'] = 0
827
828    output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
829    with open(output_file, 'w') as output:
830        output.write(Template(tmpl).render(zones=zone_config,
831                     mgr_data=manager_config))
832