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