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