xref: /openbmc/phosphor-fan-presence/control/gen-fan-zone-defs.py (revision 867a31cf865202cb3f4bb3c0c2035358624b180c)
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([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    for group in zone_data:
790        conditions = []
791        # zone conditions are optional
792        if 'zone_conditions' in group and group['zone_conditions'] is not None:
793            for c in group['zone_conditions']:
794
795                if not zone_conditions_data:
796                    sys.exit("No zone_conditions YAML file but " +
797                             "zone_conditions used in zone YAML")
798
799                condition = getConditionInZoneConditions(c['name'],
800                                                         zone_conditions_data)
801
802                if not condition:
803                    sys.exit("Missing zone condition " + c['name'])
804
805                conditions.append(condition)
806
807        zone_group = {}
808        zone_group['conditions'] = conditions
809
810        zones = []
811        for z in group['zones']:
812            zone = {}
813
814            # 'zone' is required
815            if ('zone' not in z) or (z['zone'] is None):
816                sys.exit("Missing fan zone number in " + zone_yaml)
817
818            zone['num'] = z['zone']
819
820            zone['full_speed'] = z['full_speed']
821
822            zone['default_floor'] = z['default_floor']
823
824            # 'increase_delay' is optional (use 0 by default)
825            key = 'increase_delay'
826            zone[key] = z.setdefault(key, 0)
827
828            # 'decrease_interval' is optional (use 0 by default)
829            key = 'decrease_interval'
830            zone[key] = z.setdefault(key, 0)
831
832            # 'cooling_profiles' is optional (use 'all' instead)
833            if ('cooling_profiles' not in z) or \
834                    (z['cooling_profiles'] is None):
835                profiles = ["all"]
836            else:
837                profiles = z['cooling_profiles']
838
839            # 'interfaces' is optional (no default)
840            ifaces = []
841            if ('interfaces' in z) and \
842                    (z['interfaces'] is not None):
843                ifaces = getIfacesInZone(z['interfaces'])
844
845            fans = getFansInZone(z['zone'], profiles, fan_data)
846            events = getEventsInZone(z['zone'], group['zone_conditions'],
847                                     events_data)
848
849            if len(fans) == 0:
850                sys.exit("Didn't find any fans in zone " + str(zone['num']))
851
852            if (ifaces):
853                zone['ifaces'] = ifaces
854            zone['fans'] = fans
855            zone['events'] = events
856            zones.append(zone)
857
858        zone_group['zones'] = zones
859        zone_groups.append(zone_group)
860
861    return zone_groups
862
863
864if __name__ == '__main__':
865    parser = ArgumentParser(
866        description="Phosphor fan zone definition parser")
867
868    parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
869                        default="example/zones.yaml",
870                        help='fan zone definitional yaml')
871    parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
872                        default="example/fans.yaml",
873                        help='fan definitional yaml')
874    parser.add_argument('-e', '--events_yaml', dest='events_yaml',
875                        help='events to set speeds yaml')
876    parser.add_argument('-c', '--zone_conditions_yaml',
877                        dest='zone_conditions_yaml',
878                        help='conditions to determine zone yaml')
879    parser.add_argument('-o', '--output_dir', dest='output_dir',
880                        default=".",
881                        help='output directory')
882    args = parser.parse_args()
883
884    if not args.zone_yaml or not args.fan_yaml:
885        parser.print_usage()
886        sys.exit(1)
887
888    with open(args.zone_yaml, 'r') as zone_input:
889        zone_data = yaml.safe_load(zone_input) or {}
890
891    with open(args.fan_yaml, 'r') as fan_input:
892        fan_data = yaml.safe_load(fan_input) or {}
893
894    events_data = {}
895    if args.events_yaml:
896        with open(args.events_yaml, 'r') as events_input:
897            events_data = yaml.safe_load(events_input) or {}
898
899    zone_conditions_data = {}
900    if args.zone_conditions_yaml:
901        with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
902            zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
903
904    zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
905                                fan_data, events_data, zone_conditions_data)
906
907    manager_config = zone_data.get('manager_configuration', {})
908
909    if manager_config.get('power_on_delay') is None:
910        manager_config['power_on_delay'] = 0
911
912    tmpls_dir = os.path.join(
913        os.path.dirname(os.path.realpath(__file__)),
914        "templates")
915    output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
916    if sys.version_info < (3, 0):
917        lkup = TemplateLookup(
918            directories=tmpls_dir.split(),
919            disable_unicode=True)
920    else:
921        lkup = TemplateLookup(
922            directories=tmpls_dir.split())
923    tmpl = lkup.get_template('fan_zone_defs.mako.cpp')
924    with open(output_file, 'w') as output:
925        output.write(tmpl.render(zones=zone_config,
926                                 mgr_data=manager_config))
927