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