xref: /openbmc/phosphor-fan-presence/control/gen-fan-zone-defs.py (revision 1b4de26acb4d7732f86dcdbe78338ed944d962dd)
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    e += "},\n"
173
174    e += "std::vector<Signal>{\n"
175    for s in event['triggers']['signals']:
176        e += "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 += "},\n"
204    e += "}\n"
205
206    e += "}"
207
208    return e
209
210
211def getGroups(zNum, zCond, edata, events):
212    """
213    Extract and construct the groups for the given event.
214    """
215    groups = []
216    for eGroups in edata['groups']:
217        if ('zone_conditions' in eGroups) and \
218           (eGroups['zone_conditions'] is not None):
219            # Zone conditions are optional in the events yaml but skip
220            # if this event's condition is not in this zone's conditions
221            if all('name' in z and z['name'] is not None and
222                   not any(c['name'] == z['name'] for c in zCond)
223                   for z in eGroups['zone_conditions']):
224                continue
225
226            # Zone numbers are optional in the events yaml but skip if this
227            # zone's zone number is not in the event's zone numbers
228            if all('zones' in z and z['zones'] is not None and
229                   zNum not in z['zones']
230                   for z in eGroups['zone_conditions']):
231                continue
232        eGroup = next(g for g in events['groups']
233                      if g['name'] == eGroups['name'])
234
235        group = {}
236        members = []
237        group['name'] = eGroup['name']
238        for m in eGroup['members']:
239            member = {}
240            member['path'] = eGroup['type']
241            member['object'] = (eGroup['type'] + m)
242            member['interface'] = eGroups['interface']
243            member['property'] = eGroups['property']['name']
244            member['type'] = eGroups['property']['type']
245            # Use defined service to note member on zone object
246            if ('service' in eGroup) and \
247               (eGroup['service'] is not None):
248                member['service'] = eGroup['service']
249            # Add expected group member's property value if given
250            if ('value' in eGroups['property']) and \
251               (eGroups['property']['value'] is not None):
252                    if isinstance(eGroups['property']['value'], str) or \
253                            "string" in str(member['type']).lower():
254                        member['value'] = (
255                            "\"" + eGroups['property']['value'] + "\"")
256                    else:
257                        member['value'] = eGroups['property']['value']
258            members.append(member)
259        group['members'] = members
260        groups.append(group)
261    return groups
262
263
264def getSignal(eGrps, eTrig, events):
265    """
266    Extracts and constructs for each group member a signal
267    subscription of each match listed in the trigger.
268    """
269    signals = []
270    for group in eGrps:
271        for member in group['members']:
272            for eMatches in eTrig['matches']:
273                signal = {}
274                eMatch = next(m for m in events['matches']
275                              if m['name'] == eMatches['name'])
276                # If service not given, subscribe to signal match
277                if ('service' not in member):
278                    signal['match'] = eMatch['name']
279                    params = []
280                    if ('parameters' in eMatch) and \
281                       (eMatch['parameters'] is not None):
282                        for p in eMatch['parameters']:
283                            params.append(member[str(p)])
284                    signal['mparams'] = params
285
286                if ('parameters' in eMatch['signal']) and \
287                   (eMatch['signal']['parameters'] is not None):
288                    eSignal = eMatch['signal']
289                else:
290                    eSignal = next(s for s in events['signals']
291                                   if s['name'] == eMatch['signal'])
292                signal['signal'] = eSignal['name']
293                sparams = {}
294                if ('parameters' in eSignal) and \
295                   (eSignal['parameters'] is not None):
296                    splist = []
297                    for p in eSignal['parameters']:
298                        sp = str(p)
299                        if (sp != 'type'):
300                            splist.append(sp)
301                            if (sp != 'group'):
302                                sparams[sp] = "\"" + member[sp] + "\""
303                            else:
304                                sparams[sp] = "Group{\n"
305                                for m in group['members']:
306                                    sparams[sp] += (
307                                        "{\n" +
308                                        "\"" + str(m['object']) + "\",\n" +
309                                        "{\"" + str(m['interface']) + "\"," +
310                                        "\"" + str(m['property']) + "\"}\n" +
311                                        "},\n")
312                                sparams[sp] += "}"
313                        else:
314                            sparams[sp] = member[sp]
315                    sparams['params'] = splist
316                signal['sparams'] = sparams
317                # Add signal handler
318                eHandler = next(h for h in events['handlers']
319                                if h['name'] == eSignal['handler'])
320                signal['handler'] = eHandler['name']
321                hparams = {}
322                if ('parameters' in eHandler) and \
323                   (eHandler['parameters'] is not None):
324                    hplist = []
325                    for p in eHandler['parameters']:
326                        hp = str(p)
327                        if (hp != 'type'):
328                            hplist.append(hp)
329                            if (hp != 'group'):
330                                hparams[hp] = "\"" + member[hp] + "\""
331                            else:
332                                hparams[hp] = "Group{\n"
333                                for m in group['members']:
334                                    hparams[hp] += (
335                                        "{\n" +
336                                        "\"" + str(m['object']) + "\",\n" +
337                                        "{\"" + str(m['interface']) + "\"," +
338                                        "\"" + str(m['property']) + "\"}\n" +
339                                        "},\n")
340                                hparams[hp] += "}"
341                        else:
342                            hparams[hp] = member[hp]
343                    hparams['params'] = hplist
344                signal['hparams'] = hparams
345                signals.append(signal)
346    return signals
347
348
349def getTimer(eTrig):
350    """
351    Extracts and constructs the required parameters for an
352    event timer.
353    """
354    timer = {}
355    timer['interval'] = (
356        "static_cast<std::chrono::microseconds>" +
357        "(" + str(eTrig['interval']) + ")")
358    timer['type'] = "TimerType::" + str(eTrig['type'])
359    return timer
360
361
362def getActions(zNum, zCond, edata, actions, events):
363    """
364    Extracts and constructs the make_action function call for
365    all the actions within the given event.
366    """
367    action = []
368    for eActions in actions['actions']:
369        actions = {}
370        eAction = next(a for a in events['actions']
371                       if a['name'] == eActions['name'])
372        actions['name'] = eAction['name']
373        params = []
374        if ('parameters' in eAction) and \
375           (eAction['parameters'] is not None):
376            for p in eAction['parameters']:
377                param = "static_cast<"
378                if type(eActions[p]) is not dict:
379                    if p == 'actions':
380                        param = "std::vector<Action>{"
381                        pActs = getActions(zNum,
382                                           zCond,
383                                           edata,
384                                           eActions,
385                                           events)
386                        for a in pActs:
387                            if (len(a['parameters']) != 0):
388                                param += (
389                                    "make_action(action::" +
390                                    a['name'] +
391                                    "(\n")
392                                for i, ap in enumerate(a['parameters']):
393                                    if (i+1) != len(a['parameters']):
394                                        param += (ap + ",")
395                                    else:
396                                        param += (ap + ")")
397                            else:
398                                param += ("make_action(action::" + a['name'])
399                            param += "),"
400                        param += "}"
401                    elif p == 'defevents' or p == 'altevents':
402                        param = "std::vector<SetSpeedEvent>{\n"
403                        for i, e in enumerate(eActions[p]):
404                            aEvent = getEvent(zNum, zCond, e, events)
405                            if not aEvent:
406                                continue
407                            if (i+1) != len(eActions[p]):
408                                param += genEvent(aEvent) + ",\n"
409                            else:
410                                param += genEvent(aEvent) + "\n"
411                        param += "\t}"
412                    elif p == 'property':
413                        if isinstance(eActions[p], str) or \
414                           "string" in str(eActions[p]['type']).lower():
415                            param += (
416                                str(eActions[p]['type']).lower() +
417                                ">(\"" + str(eActions[p]) + "\")")
418                        else:
419                            param += (
420                                str(eActions[p]['type']).lower() +
421                                ">(" + str(eActions[p]['value']).lower() + ")")
422                    else:
423                        # Default type to 'size_t' when not given
424                        param += ("size_t>(" + str(eActions[p]).lower() + ")")
425                else:
426                    if p == 'timer':
427                        t = getTimer(eActions[p])
428                        param = (
429                            "TimerConf{" + t['interval'] + "," +
430                            t['type'] + "}")
431                    else:
432                        param += (str(eActions[p]['type']).lower() + ">(")
433                        if p != 'map':
434                            if isinstance(eActions[p]['value'], str) or \
435                               "string" in str(eActions[p]['type']).lower():
436                                param += \
437                                    "\"" + str(eActions[p]['value']) + "\")"
438                            else:
439                                param += \
440                                    str(eActions[p]['value']).lower() + ")"
441                        else:
442                            param += (
443                                str(eActions[p]['type']).lower() +
444                                convertToMap(str(eActions[p]['value'])) + ")")
445                params.append(param)
446        actions['parameters'] = params
447        action.append(actions)
448    return action
449
450
451def getEvent(zone_num, zone_conditions, e, events_data):
452    """
453    Parses the sections of an event and populates the properties
454    that construct an event within the generated source.
455    """
456    event = {}
457
458    # Add set speed event groups
459    grps = getGroups(zone_num, zone_conditions, e, events_data)
460    if not grps:
461        return
462    event['groups'] = grps
463
464    # Add optional set speed actions and function parameters
465    event['action'] = []
466    if ('actions' in e) and \
467       (e['actions'] is not None):
468        event['action'] = getActions(zone_num,
469                                     zone_conditions,
470                                     e,
471                                     e,
472                                     events_data)
473
474    # Add event triggers
475    event['triggers'] = {}
476    for trig in e['triggers']:
477        triggers = []
478        if (trig['name'] == "timer"):
479            event['triggers']['timer'] = getTimer(trig)
480        elif (trig['name'] == "signal"):
481            if ('signals' not in event['triggers']):
482                event['triggers']['signals'] = []
483            triggers = getSignal(event['groups'], trig, events_data)
484            event['triggers']['signals'].extend(triggers)
485
486    return event
487
488
489def addPrecondition(zNum, zCond, event, events_data):
490    """
491    Parses the precondition section of an event and populates the necessary
492    structures to generate a precondition for a set speed event.
493    """
494    precond = {}
495    # Add set speed event precondition group
496    grps = getGroups(zNum, zCond, event['precondition'], events_data)
497    if not grps:
498        return
499    precond['pcgrps'] = grps
500
501    # Add set speed event precondition actions
502    pc = []
503    pcs = {}
504    pcs['name'] = event['precondition']['name']
505    epc = next(p for p in events_data['preconditions']
506               if p['name'] == event['precondition']['name'])
507    params = []
508    for p in epc['parameters']:
509        param = {}
510        if p == 'groups':
511            param['type'] = "std::vector<PrecondGroup>"
512            param['open'] = "{"
513            param['close'] = "}"
514            values = []
515            for group in precond['pcgrps']:
516                for pcgrp in group['members']:
517                    value = {}
518                    value['value'] = (
519                        "PrecondGroup{\"" +
520                        str(pcgrp['object']) + "\",\"" +
521                        str(pcgrp['interface']) + "\",\"" +
522                        str(pcgrp['property']) + "\"," +
523                        "static_cast<" +
524                        str(pcgrp['type']).lower() + ">")
525                    if isinstance(pcgrp['value'], str) or \
526                       "string" in str(pcgrp['type']).lower():
527                        value['value'] += ("(" + str(pcgrp['value']) + ")}")
528                    else:
529                        value['value'] += \
530                            ("(" + str(pcgrp['value']).lower() + ")}")
531                    values.append(value)
532            param['values'] = values
533        params.append(param)
534    pcs['params'] = params
535    pc.append(pcs)
536    precond['pcact'] = pc
537
538    pcevents = []
539    for pce in event['precondition']['events']:
540        pcevent = getEvent(zNum, zCond, pce, events_data)
541        if not pcevent:
542            continue
543        pcevents.append(pcevent)
544    precond['pcevts'] = pcevents
545
546    # Add precondition event triggers
547    precond['triggers'] = {}
548    for trig in event['precondition']['triggers']:
549        triggers = []
550        if (trig['name'] == "timer"):
551            precond['triggers']['pctime'] = getTimer(trig)
552        elif (trig['name'] == "signal"):
553            if ('pcsigs' not in precond['triggers']):
554                precond['triggers']['pcsigs'] = []
555            triggers = getSignal(precond['pcgrps'], trig, events_data)
556            precond['triggers']['pcsigs'].extend(triggers)
557
558    return precond
559
560
561def getEventsInZone(zone_num, zone_conditions, events_data):
562    """
563    Constructs the event entries defined for each zone using the events yaml
564    provided.
565    """
566    events = []
567
568    if 'events' in events_data:
569        for e in events_data['events']:
570            event = {}
571            # Add precondition if given
572            if ('precondition' in e) and \
573               (e['precondition'] is not None):
574                event['pc'] = addPrecondition(zone_num,
575                                              zone_conditions,
576                                              e,
577                                              events_data)
578            else:
579                event = getEvent(zone_num, zone_conditions, e, events_data)
580                if not event:
581                    continue
582            events.append(event)
583
584    return events
585
586
587def getFansInZone(zone_num, profiles, fan_data):
588    """
589    Parses the fan definition YAML files to find the fans
590    that match both the zone passed in and one of the
591    cooling profiles.
592    """
593
594    fans = []
595
596    for f in fan_data['fans']:
597
598        if zone_num != f['cooling_zone']:
599            continue
600
601        # 'cooling_profile' is optional (use 'all' instead)
602        if f.get('cooling_profile') is None:
603            profile = "all"
604        else:
605            profile = f['cooling_profile']
606
607        if profile not in profiles:
608            continue
609
610        fan = {}
611        fan['name'] = f['inventory']
612        fan['sensors'] = f['sensors']
613        fan['target_interface'] = f.get(
614            'target_interface',
615            'xyz.openbmc_project.Control.FanSpeed')
616        fans.append(fan)
617
618    return fans
619
620
621def getIfacesInZone(zone_ifaces):
622    """
623    Parse given interfaces for a zone for associating a zone with an interface
624    and set any properties listed to defined values upon fan control starting
625    on the zone.
626    """
627
628    ifaces = []
629    for i in zone_ifaces:
630        iface = {}
631        # Interface name not needed yet for fan zones but
632        # may be necessary as more interfaces are extended by the zones
633        iface['name'] = i['name']
634
635        if ('properties' in i) and \
636                (i['properties'] is not None):
637            props = []
638            for p in i['properties']:
639                prop = {}
640                prop['name'] = p['name']
641                prop['func'] = str(p['name']).lower()
642                prop['type'] = parse_cpp_type(p['type'])
643                if ('persist' in p):
644                    persist = p['persist']
645                    if (persist is not None):
646                        if (isinstance(persist, bool)):
647                            prop['persist'] = 'true' if persist else 'false'
648                else:
649                    prop['persist'] = 'false'
650                vals = []
651                for v in p['values']:
652                    val = v['value']
653                    if (val is not None):
654                        if (isinstance(val, bool)):
655                            # Convert True/False to 'true'/'false'
656                            val = 'true' if val else 'false'
657                        elif (isinstance(val, str)):
658                            # Wrap strings with double-quotes
659                            val = "\"" + val + "\""
660                        vals.append(val)
661                prop['values'] = vals
662                props.append(prop)
663            iface['props'] = props
664        ifaces.append(iface)
665
666    return ifaces
667
668
669def getConditionInZoneConditions(zone_condition, zone_conditions_data):
670    """
671    Parses the zone conditions definition YAML files to find the condition
672    that match both the zone condition passed in.
673    """
674
675    condition = {}
676
677    for c in zone_conditions_data['conditions']:
678
679        if zone_condition != c['name']:
680            continue
681        condition['type'] = c['type']
682        properties = []
683        for p in c['properties']:
684            property = {}
685            property['property'] = p['property']
686            property['interface'] = p['interface']
687            property['path'] = p['path']
688            property['type'] = p['type'].lower()
689            property['value'] = str(p['value']).lower()
690            properties.append(property)
691        condition['properties'] = properties
692
693        return condition
694
695
696def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
697    """
698    Combines the zone definition YAML and fan
699    definition YAML to create a data structure defining
700    the fan cooling zones.
701    """
702
703    zone_groups = []
704
705    for group in zone_data:
706        conditions = []
707        # zone conditions are optional
708        if 'zone_conditions' in group and group['zone_conditions'] is not None:
709            for c in group['zone_conditions']:
710
711                if not zone_conditions_data:
712                    sys.exit("No zone_conditions YAML file but " +
713                             "zone_conditions used in zone YAML")
714
715                condition = getConditionInZoneConditions(c['name'],
716                                                         zone_conditions_data)
717
718                if not condition:
719                    sys.exit("Missing zone condition " + c['name'])
720
721                conditions.append(condition)
722
723        zone_group = {}
724        zone_group['conditions'] = conditions
725
726        zones = []
727        for z in group['zones']:
728            zone = {}
729
730            # 'zone' is required
731            if ('zone' not in z) or (z['zone'] is None):
732                sys.exit("Missing fan zone number in " + zone_yaml)
733
734            zone['num'] = z['zone']
735
736            zone['full_speed'] = z['full_speed']
737
738            zone['default_floor'] = z['default_floor']
739
740            # 'increase_delay' is optional (use 0 by default)
741            key = 'increase_delay'
742            zone[key] = z.setdefault(key, 0)
743
744            # 'decrease_interval' is optional (use 0 by default)
745            key = 'decrease_interval'
746            zone[key] = z.setdefault(key, 0)
747
748            # 'cooling_profiles' is optional (use 'all' instead)
749            if ('cooling_profiles' not in z) or \
750                    (z['cooling_profiles'] is None):
751                profiles = ["all"]
752            else:
753                profiles = z['cooling_profiles']
754
755            # 'interfaces' is optional (no default)
756            ifaces = []
757            if ('interfaces' in z) and \
758                    (z['interfaces'] is not None):
759                ifaces = getIfacesInZone(z['interfaces'])
760
761            fans = getFansInZone(z['zone'], profiles, fan_data)
762            events = getEventsInZone(z['zone'], group['zone_conditions'],
763                                     events_data)
764
765            if len(fans) == 0:
766                sys.exit("Didn't find any fans in zone " + str(zone['num']))
767
768            if (ifaces):
769                zone['ifaces'] = ifaces
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    tmpls_dir = os.path.join(
829        os.path.dirname(os.path.realpath(__file__)),
830        "templates")
831    output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
832    if sys.version_info < (3, 0):
833        lkup = TemplateLookup(
834            directories=tmpls_dir.split(),
835            disable_unicode=True)
836    else:
837        lkup = TemplateLookup(
838            directories=tmpls_dir.split())
839    tmpl = lkup.get_template('fan_zone_defs.mako.cpp')
840    with open(output_file, 'w') as output:
841        output.write(tmpl.render(zones=zone_config,
842                                 mgr_data=manager_config))
843