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