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