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