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        fan['target_path'] = f.get(
701            'target_path',
702            '/xyz/openbmc_project/sensors/fan_tach/')
703        fans.append(fan)
704
705    return fans
706
707
708def getIfacesInZone(zone_ifaces):
709    """
710    Parse given interfaces for a zone for associating a zone with an interface
711    and set any properties listed to defined values upon fan control starting
712    on the zone.
713    """
714
715    ifaces = []
716    for i in zone_ifaces:
717        iface = {}
718        # Interface name not needed yet for fan zones but
719        # may be necessary as more interfaces are extended by the zones
720        iface['name'] = i['name']
721
722        if ('properties' in i) and \
723                (i['properties'] is not None):
724            props = []
725            for p in i['properties']:
726                prop = {}
727                prop['name'] = p['name']
728                prop['func'] = str(p['name']).lower()
729                prop['type'] = parse_cpp_type(p['type'])
730                if ('persist' in p):
731                    persist = p['persist']
732                    if (persist is not None):
733                        if (isinstance(persist, bool)):
734                            prop['persist'] = 'true' if persist else 'false'
735                else:
736                    prop['persist'] = 'false'
737                vals = []
738                for v in p['values']:
739                    val = v['value']
740                    if (val is not None):
741                        if (isinstance(val, bool)):
742                            # Convert True/False to 'true'/'false'
743                            val = 'true' if val else 'false'
744                        elif (isinstance(val, str)):
745                            # Wrap strings with double-quotes
746                            val = "\"" + val + "\""
747                        vals.append(val)
748                prop['values'] = vals
749                props.append(prop)
750            iface['props'] = props
751        ifaces.append(iface)
752
753    return ifaces
754
755
756def getConditionInZoneConditions(zone_condition, zone_conditions_data):
757    """
758    Parses the zone conditions definition YAML files to find the condition
759    that match both the zone condition passed in.
760    """
761
762    condition = {}
763
764    for c in zone_conditions_data['conditions']:
765
766        if zone_condition != c['name']:
767            continue
768        condition['type'] = c['type']
769        properties = []
770        for p in c['properties']:
771            property = {}
772            property['property'] = p['property']
773            property['interface'] = p['interface']
774            property['path'] = p['path']
775            property['type'] = p['type'].lower()
776            property['value'] = str(p['value']).lower()
777            properties.append(property)
778        condition['properties'] = properties
779
780        return condition
781
782
783def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
784    """
785    Combines the zone definition YAML and fan
786    definition YAML to create a data structure defining
787    the fan cooling zones.
788    """
789
790    zone_groups = []
791
792    # Allow zone_conditions to not be in yaml (since its optional)
793    if not isinstance(zone_data, list) and zone_data != {}:
794        zone_data = [zone_data]
795    for group in zone_data:
796        conditions = []
797        # zone conditions are optional
798        if 'zone_conditions' in group and group['zone_conditions'] is not None:
799            for c in group['zone_conditions']:
800
801                if not zone_conditions_data:
802                    sys.exit("No zone_conditions YAML file but " +
803                             "zone_conditions used in zone YAML")
804
805                condition = getConditionInZoneConditions(c['name'],
806                                                         zone_conditions_data)
807
808                if not condition:
809                    sys.exit("Missing zone condition " + c['name'])
810
811                conditions.append(condition)
812
813        zone_group = {}
814        zone_group['conditions'] = conditions
815
816        zones = []
817        for z in group['zones']:
818            zone = {}
819
820            # 'zone' is required
821            if ('zone' not in z) or (z['zone'] is None):
822                sys.exit("Missing fan zone number in " + zone_yaml)
823
824            zone['num'] = z['zone']
825
826            zone['full_speed'] = z['full_speed']
827
828            zone['default_floor'] = z['default_floor']
829
830            # 'increase_delay' is optional (use 0 by default)
831            key = 'increase_delay'
832            zone[key] = z.setdefault(key, 0)
833
834            # 'decrease_interval' is optional (use 0 by default)
835            key = 'decrease_interval'
836            zone[key] = z.setdefault(key, 0)
837
838            # 'cooling_profiles' is optional (use 'all' instead)
839            if ('cooling_profiles' not in z) or \
840                    (z['cooling_profiles'] is None):
841                profiles = ["all"]
842            else:
843                profiles = z['cooling_profiles']
844
845            # 'interfaces' is optional (no default)
846            ifaces = []
847            if ('interfaces' in z) and \
848                    (z['interfaces'] is not None):
849                ifaces = getIfacesInZone(z['interfaces'])
850
851            fans = getFansInZone(z['zone'], profiles, fan_data)
852            events = getEventsInZone(z['zone'],
853                                     group.get('zone_conditions', {}),
854                                     events_data)
855
856            if len(fans) == 0:
857                sys.exit("Didn't find any fans in zone " + str(zone['num']))
858
859            if (ifaces):
860                zone['ifaces'] = ifaces
861            zone['fans'] = fans
862            zone['events'] = events
863            zones.append(zone)
864
865        zone_group['zones'] = zones
866        zone_groups.append(zone_group)
867
868    return zone_groups
869
870
871if __name__ == '__main__':
872    parser = ArgumentParser(
873        description="Phosphor fan zone definition parser")
874
875    parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
876                        default="example/zones.yaml",
877                        help='fan zone definitional yaml')
878    parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
879                        default="example/fans.yaml",
880                        help='fan definitional yaml')
881    parser.add_argument('-e', '--events_yaml', dest='events_yaml',
882                        help='events to set speeds yaml')
883    parser.add_argument('-c', '--zone_conditions_yaml',
884                        dest='zone_conditions_yaml',
885                        help='conditions to determine zone yaml')
886    parser.add_argument('-o', '--output_dir', dest='output_dir',
887                        default=".",
888                        help='output directory')
889    args = parser.parse_args()
890
891    if not args.zone_yaml or not args.fan_yaml:
892        parser.print_usage()
893        sys.exit(1)
894
895    with open(args.zone_yaml, 'r') as zone_input:
896        zone_data = yaml.safe_load(zone_input) or {}
897
898    with open(args.fan_yaml, 'r') as fan_input:
899        fan_data = yaml.safe_load(fan_input) or {}
900
901    events_data = {}
902    if args.events_yaml:
903        with open(args.events_yaml, 'r') as events_input:
904            events_data = yaml.safe_load(events_input) or {}
905
906    zone_conditions_data = {}
907    if args.zone_conditions_yaml:
908        with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
909            zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
910
911    zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
912                                fan_data, events_data, zone_conditions_data)
913
914    manager_config = zone_data.get('manager_configuration', {})
915
916    if manager_config.get('power_on_delay') is None:
917        manager_config['power_on_delay'] = 0
918
919    tmpls_dir = os.path.join(
920        os.path.dirname(os.path.realpath(__file__)),
921        "templates")
922    output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
923    if sys.version_info < (3, 0):
924        lkup = TemplateLookup(
925            directories=tmpls_dir.split(),
926            disable_unicode=True)
927    else:
928        lkup = TemplateLookup(
929            directories=tmpls_dir.split())
930    tmpl = lkup.get_template('fan_zone_defs.mako.cpp')
931    with open(output_file, 'w') as output:
932        output.write(tmpl.render(zones=zone_config,
933                                 mgr_data=manager_config))
934