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