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