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