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