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