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