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