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