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