xref: /openbmc/phosphor-fan-presence/control/gen-fan-zone-defs.py (revision a1aef7a100a9b27d2133622a695bb8c09c2869bf)
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 convertToMap(listOfDict):
17    """
18    Converts a list of dictionary entries to a std::map initialization list.
19    """
20    listOfDict = listOfDict.replace('\'', '\"')
21    listOfDict = listOfDict.replace('[', '{')
22    listOfDict = listOfDict.replace(']', '}')
23    listOfDict = listOfDict.replace(':', ',')
24    return listOfDict
25
26
27def genEvent(event):
28    """
29    Generates the source code of an event and returns it as a string
30    """
31    e = "SetSpeedEvent{\n"
32
33    e += "Group{\n"
34    for group in event['groups']:
35        for member in group['members']:
36            e += "{\n"
37            e += "\"" + member['object'] + "\",\n"
38            e += "{\"" + member['interface'] + "\",\n"
39            e += " \"" + member['property'] + "\"}\n"
40        e += "},\n"
41    e += "},\n"
42
43    e += "std::vector<Action>{\n"
44    for a in event['action']:
45        if len(a['parameters']) != 0:
46            e += "make_action(action::" + a['name'] + "(\n"
47        else:
48            e += "make_action(action::" + a['name'] + "\n"
49        for i, p in enumerate(a['parameters']):
50            if (i+1) != len(a['parameters']):
51                e += p + ",\n"
52            else:
53                e += p + ")\n"
54        e += "),\n"
55    e += "},\n"
56
57    e += "TimerConf{\n"
58    e += "\t" + event['timer']['interval'] + ",\n"
59    e += "\t" + event['timer']['type'] + "\n"
60    e += "},\n"
61
62    e += "std::vector<Signal>{\n"
63    for s in event['signals']:
64        e += "Signal{\n"
65        e += "match::" + s['match'] + "(\n"
66        for i, mp in enumerate(s['mparams']):
67            if (i+1) != len(s['mparams']):
68                e += "\"" + mp + "\",\n"
69            else:
70                e += "\"" + mp + "\"\n"
71        e += "),\n"
72        e += "make_handler(\n"
73        if ('type' in s['sparams']) and (s['sparams']['type'] is not None):
74            e += s['signal'] + "<" + s['sparams']['type'] + ">(\n"
75        else:
76            e += s['signal'] + "(\n"
77        for sp in s['sparams']['params']:
78            e += s['sparams'][sp] + ",\n"
79        if ('type' in s['hparams']) and (s['hparams']['type'] is not None):
80            e += ("handler::" + s['handler'] +
81                  "<" + s['hparams']['type'] + ">(\n")
82        else:
83            e += "handler::" + s['handler'] + "(\n)"
84        for i, hp in enumerate(s['hparams']['params']):
85            if (i+1) != len(s['hparams']['params']):
86                e += s['hparams'][hp] + ",\n"
87            else:
88                e += s['hparams'][hp] + "\n"
89        e += "))\n"
90        e += ")\n"
91    e += "},\n"
92    e += "}\n"
93
94    e += "}"
95
96    return e
97
98
99def getGroups(zNum, zCond, edata, events):
100    """
101    Extract and construct the groups for the given event.
102    """
103    groups = []
104    for eGroups in edata['groups']:
105        if ('zone_conditions' in eGroups) and \
106           (eGroups['zone_conditions'] is not None):
107            # Zone conditions are optional in the events yaml but skip
108            # if this event's condition is not in this zone's conditions
109            if all('name' in z and z['name'] is not None and
110                   not any(c['name'] == z['name'] for c in zCond)
111                   for z in eGroups['zone_conditions']):
112                continue
113
114            # Zone numbers are optional in the events yaml but skip if this
115            # zone's zone number is not in the event's zone numbers
116            if all('zones' in z and z['zones'] is not None and
117                   zNum not in z['zones']
118                   for z in eGroups['zone_conditions']):
119                continue
120        eGroup = next(g for g in events['groups']
121                      if g['name'] == eGroups['name'])
122
123        group = {}
124        members = []
125        group['name'] = eGroup['name']
126        for m in eGroup['members']:
127            member = {}
128            member['path'] = eGroup['type']
129            member['object'] = (eGroup['type'] + m)
130            member['interface'] = eGroups['interface']
131            member['property'] = eGroups['property']['name']
132            member['type'] = eGroups['property']['type']
133            # Add expected group member's property value if given
134            if ('value' in eGroups['property']) and \
135               (eGroups['property']['value'] is not None):
136                    if isinstance(eGroups['property']['value'], str) or \
137                            "string" in str(member['type']).lower():
138                        member['value'] = (
139                            "\"" + eGroups['property']['value'] + "\"")
140                    else:
141                        member['value'] = eGroups['property']['value']
142            members.append(member)
143        group['members'] = members
144        groups.append(group)
145    return groups
146
147
148def getActions(zNum, zCond, edata, actions, events):
149    """
150    Extracts and constructs the make_action function call for
151    all the actions within the given event.
152    """
153    action = []
154    for eActions in actions['actions']:
155        actions = {}
156        eAction = next(a for a in events['actions']
157                       if a['name'] == eActions['name'])
158        actions['name'] = eAction['name']
159        params = []
160        if ('parameters' in eAction) and \
161           (eAction['parameters'] is not None):
162            for p in eAction['parameters']:
163                param = "static_cast<"
164                if type(eActions[p]) is not dict:
165                    if p == 'actions':
166                        param = "std::vector<Action>{"
167                        pActs = getActions(zNum,
168                                           zCond,
169                                           edata,
170                                           eActions,
171                                           events)
172                        for a in pActs:
173                            if (len(a['parameters']) != 0):
174                                param += (
175                                    "make_action(action::" +
176                                    a['name'] +
177                                    "(\n")
178                                for i, ap in enumerate(a['parameters']):
179                                    if (i+1) != len(a['parameters']):
180                                        param += (ap + ",")
181                                    else:
182                                        param += (ap + ")")
183                            else:
184                                param += ("make_action(action::" + a['name'])
185                            param += "),"
186                        param += "}"
187                    elif p == 'defevents' or p == 'altevents':
188                        param = "std::vector<SetSpeedEvent>{\n"
189                        for i, e in enumerate(eActions[p]):
190                            aEvent = getEvent(zNum, zCond, e, events)
191                            if not aEvent:
192                                continue
193                            if (i+1) != len(eActions[p]):
194                                param += genEvent(aEvent) + ",\n"
195                            else:
196                                param += genEvent(aEvent) + "\n"
197                        param += "\t}"
198                    elif p == 'property':
199                        if isinstance(eActions[p], str) or \
200                           "string" in str(eActions[p]['type']).lower():
201                            param += (
202                                str(eActions[p]['type']).lower() +
203                                ">(\"" + str(eActions[p]) + "\")")
204                        else:
205                            param += (
206                                str(eActions[p]['type']).lower() +
207                                ">(" + str(eActions[p]['value']).lower() + ")")
208                    else:
209                        # Default type to 'size_t' when not given
210                        param += ("size_t>(" + str(eActions[p]).lower() + ")")
211                else:
212                    if p == 'timer':
213                        param = (
214                            "TimerConf{static_cast<std::chrono::seconds>(" +
215                            str(eActions[p]['delay']) + "), " +
216                            "TimerType::" +
217                            str(eActions[p]['type']) + "}")
218                    else:
219                        param += (str(eActions[p]['type']).lower() + ">(")
220                        if p != 'map':
221                            if isinstance(eActions[p]['value'], str) or \
222                               "string" in str(eActions[p]['type']).lower():
223                                param += \
224                                    "\"" + str(eActions[p]['value']) + "\")"
225                            else:
226                                param += \
227                                    str(eActions[p]['value']).lower() + ")"
228                        else:
229                            param += (
230                                str(eActions[p]['type']).lower() +
231                                convertToMap(str(eActions[p]['value'])) + ")")
232                params.append(param)
233        actions['parameters'] = params
234        action.append(actions)
235    return action
236
237
238def getEvent(zone_num, zone_conditions, e, events_data):
239    """
240    Parses the sections of an event and populates the properties
241    that construct an event within the generated source.
242    """
243    event = {}
244
245    # Add set speed event groups
246    grps = getGroups(zone_num, zone_conditions, e, events_data)
247    if not grps:
248        return
249    event['groups'] = grps
250
251    # Add optional set speed actions and function parameters
252    event['action'] = []
253    if ('actions' in e) and \
254       (e['actions'] is not None):
255        event['action'] = getActions(zone_num,
256                                     zone_conditions,
257                                     e,
258                                     e,
259                                     events_data)
260
261    # Add signal handlers
262    signals = []
263    for group in event['groups']:
264        for member in group['members']:
265            for eMatches in e['matches']:
266                signal = {}
267                eMatch = next(m for m in events_data['matches']
268                              if m['name'] == eMatches['name'])
269                signal['match'] = eMatch['name']
270                params = []
271                if ('parameters' in eMatch) and \
272                   (eMatch['parameters'] is not None):
273                    for p in eMatch['parameters']:
274                        params.append(member[str(p)])
275                signal['mparams'] = params
276                if ('parameters' in eMatch['signal']) and \
277                   (eMatch['signal']['parameters'] is not None):
278                    eSignal = eMatch['signal']
279                else:
280                    eSignal = next(s for s in events_data['signals']
281                                   if s['name'] == eMatch['signal'])
282                signal['signal'] = eSignal['name']
283                sparams = {}
284                if ('parameters' in eSignal) and \
285                   (eSignal['parameters'] is not None):
286                    splist = []
287                    for p in eSignal['parameters']:
288                        sp = str(p)
289                        if (sp != 'type'):
290                            splist.append(sp)
291                            if (sp != 'group'):
292                                sparams[sp] = "\"" + member[sp] + "\""
293                            else:
294                                sparams[sp] = "Group{\n"
295                                for m in group['members']:
296                                    sparams[sp] += (
297                                        "{\n" +
298                                        "\"" + str(m['object']) + "\",\n" +
299                                        "{\"" + str(m['interface']) + "\"," +
300                                        "\"" + str(m['property']) + "\"}\n" +
301                                        "},\n")
302                                sparams[sp] += "}"
303                        else:
304                            sparams[sp] = member[sp]
305                    sparams['params'] = splist
306                signal['sparams'] = sparams
307                # Add signal handler
308                eHandler = next(h for h in events_data['handlers']
309                                if h['name'] == eSignal['handler'])
310                signal['handler'] = eHandler['name']
311                hparams = {}
312                if ('parameters' in eHandler) and \
313                   (eHandler['parameters'] is not None):
314                    hplist = []
315                    for p in eHandler['parameters']:
316                        hp = str(p)
317                        if (hp != 'type'):
318                            hplist.append(hp)
319                            if (hp != 'group'):
320                                hparams[hp] = "\"" + member[hp] + "\""
321                            else:
322                                hparams[hp] = "Group{\n"
323                                for m in group['members']:
324                                    hparams[hp] += (
325                                        "{\n" +
326                                        "\"" + str(m['object']) + "\",\n" +
327                                        "{\"" + str(m['interface']) + "\"," +
328                                        "\"" + str(m['property']) + "\"}\n" +
329                                        "},\n")
330                                hparams[hp] += "}"
331                        else:
332                            hparams[hp] = member[hp]
333                    hparams['params'] = hplist
334                signal['hparams'] = hparams
335                signals.append(signal)
336    event['signals'] = signals
337
338    # Add optional action call timer
339    timer = {}
340    interval = "static_cast<std::chrono::seconds>"
341    if ('timer' in e) and \
342       (e['timer'] is not None):
343        timer['interval'] = (interval +
344                             "(" +
345                             str(e['timer']['interval']) +
346                             ")")
347    else:
348        timer['interval'] = (interval +
349                             "(" + str(0) + ")")
350    timer['type'] = "TimerType::repeating"
351    event['timer'] = timer
352
353    return event
354
355
356def addPrecondition(zNum, zCond, event, events_data):
357    """
358    Parses the precondition section of an event and populates the necessary
359    structures to generate a precondition for a set speed event.
360    """
361    precond = {}
362    # Add set speed event precondition group
363    grps = getGroups(zNum, zCond, event['precondition'], events_data)
364    if not grps:
365        return
366    precond['pcgrps'] = grps
367
368    # Add set speed event precondition actions
369    pc = []
370    pcs = {}
371    pcs['name'] = event['precondition']['name']
372    epc = next(p for p in events_data['preconditions']
373               if p['name'] == event['precondition']['name'])
374    params = []
375    for p in epc['parameters']:
376        param = {}
377        if p == 'groups':
378            param['type'] = "std::vector<PrecondGroup>"
379            param['open'] = "{"
380            param['close'] = "}"
381            values = []
382            for group in precond['pcgrps']:
383                for pcgrp in group['members']:
384                    value = {}
385                    value['value'] = (
386                        "PrecondGroup{\"" +
387                        str(pcgrp['object']) + "\",\"" +
388                        str(pcgrp['interface']) + "\",\"" +
389                        str(pcgrp['property']) + "\"," +
390                        "static_cast<" +
391                        str(pcgrp['type']).lower() + ">")
392                    if isinstance(pcgrp['value'], str) or \
393                       "string" in str(pcgrp['type']).lower():
394                        value['value'] += ("(" + str(pcgrp['value']) + ")}")
395                    else:
396                        value['value'] += \
397                            ("(" + str(pcgrp['value']).lower() + ")}")
398                    values.append(value)
399            param['values'] = values
400        params.append(param)
401    pcs['params'] = params
402    pc.append(pcs)
403    precond['pcact'] = pc
404
405    pcevents = []
406    for pce in event['precondition']['events']:
407        pcevent = getEvent(zNum, zCond, pce, events_data)
408        if not pcevent:
409            continue
410        pcevents.append(pcevent)
411    precond['pcevts'] = pcevents
412
413    # Add precondition signal handlers
414    signals = []
415    for group in precond['pcgrps']:
416        for member in group['members']:
417            for eMatches in event['precondition']['matches']:
418                signal = {}
419                eMatch = next(m for m in events_data['matches']
420                              if m['name'] == eMatches['name'])
421                signal['match'] = eMatch['name']
422                params = []
423                if ('parameters' in eMatch) and \
424                   (eMatch['parameters'] is not None):
425                    for p in eMatch['parameters']:
426                        params.append(member[str(p)])
427                signal['mparams'] = params
428                if ('parameters' in eMatch['signal']) and \
429                   (eMatch['signal']['parameters'] is not None):
430                    eSignal = eMatch['signal']
431                else:
432                    eSignal = next(s for s in events_data['signals']
433                                   if s['name'] == eMatch['signal'])
434                signal['signal'] = eSignal['name']
435                sparams = {}
436                if ('parameters' in eSignal) and \
437                   (eSignal['parameters'] is not None):
438                    splist = []
439                    for p in eSignal['parameters']:
440                        sp = str(p)
441                        if (sp != 'type'):
442                            splist.append(sp)
443                            if (sp != 'group'):
444                                sparams[sp] = "\"" + member[sp] + "\""
445                            else:
446                                sparams[sp] = "Group{\n"
447                                for m in group:
448                                    sparams[sp] += (
449                                        "{\n" +
450                                        "\"" + str(m['object']) + "\",\n" +
451                                        "{\"" + str(m['interface']) + "\"," +
452                                        "\"" + str(m['property']) + "\"}\n" +
453                                        "},\n")
454                                sparams[sp] += "}"
455                        else:
456                            sparams[sp] = member[sp]
457                    sparams['params'] = splist
458                signal['sparams'] = sparams
459                # Add signal handler
460                eHandler = next(h for h in events_data['handlers']
461                                if h['name'] == eSignal['handler'])
462                signal['handler'] = eHandler['name']
463                hparams = {}
464                if ('parameters' in eHandler) and \
465                   (eHandler['parameters'] is not None):
466                    hplist = []
467                    for p in eHandler['parameters']:
468                        hp = str(p)
469                        if (hp != 'type'):
470                            hplist.append(hp)
471                            if (hp != 'group'):
472                                hparams[hp] = "\"" + member[hp] + "\""
473                            else:
474                                hparams[hp] = "Group{\n"
475                                for m in group:
476                                    hparams[hp] += (
477                                        "{\n" +
478                                        "\"" + str(m['object']) + "\",\n" +
479                                        "{\"" + str(m['interface']) + "\"," +
480                                        "\"" + str(m['property']) + "\"}\n" +
481                                        "},\n")
482                                hparams[hp] += "}"
483                        else:
484                            hparams[hp] = member[hp]
485                    hparams['params'] = hplist
486                signal['hparams'] = hparams
487                signals.append(signal)
488    precond['pcsigs'] = signals
489
490    # Add optional action call timer
491    timer = {}
492    interval = "static_cast<std::chrono::seconds>"
493    if ('timer' in event['precondition']) and \
494       (event['precondition']['timer'] is not None):
495        timer['interval'] = (interval +
496                             "(" +
497                             str(event['precondition']['timer']['interval']) +
498                             ")")
499    else:
500        timer['interval'] = (interval +
501                             "(" + str(0) + ")")
502    timer['type'] = "TimerType::repeating"
503    precond['pctime'] = timer
504
505    return precond
506
507
508def getEventsInZone(zone_num, zone_conditions, events_data):
509    """
510    Constructs the event entries defined for each zone using the events yaml
511    provided.
512    """
513    events = []
514
515    if 'events' in events_data:
516        for e in events_data['events']:
517            event = {}
518            # Add precondition if given
519            if ('precondition' in e) and \
520               (e['precondition'] is not None):
521                event['pc'] = addPrecondition(zone_num,
522                                              zone_conditions,
523                                              e,
524                                              events_data)
525            else:
526                event = getEvent(zone_num, zone_conditions, e, events_data)
527                if not event:
528                    continue
529            events.append(event)
530
531    return events
532
533
534def getFansInZone(zone_num, profiles, fan_data):
535    """
536    Parses the fan definition YAML files to find the fans
537    that match both the zone passed in and one of the
538    cooling profiles.
539    """
540
541    fans = []
542
543    for f in fan_data['fans']:
544
545        if zone_num != f['cooling_zone']:
546            continue
547
548        # 'cooling_profile' is optional (use 'all' instead)
549        if f.get('cooling_profile') is None:
550            profile = "all"
551        else:
552            profile = f['cooling_profile']
553
554        if profile not in profiles:
555            continue
556
557        fan = {}
558        fan['name'] = f['inventory']
559        fan['sensors'] = f['sensors']
560        fan['target_interface'] = f.get(
561            'target_interface',
562            'xyz.openbmc_project.Control.FanSpeed')
563        fans.append(fan)
564
565    return fans
566
567
568def getConditionInZoneConditions(zone_condition, zone_conditions_data):
569    """
570    Parses the zone conditions definition YAML files to find the condition
571    that match both the zone condition passed in.
572    """
573
574    condition = {}
575
576    for c in zone_conditions_data['conditions']:
577
578        if zone_condition != c['name']:
579            continue
580        condition['type'] = c['type']
581        properties = []
582        for p in c['properties']:
583            property = {}
584            property['property'] = p['property']
585            property['interface'] = p['interface']
586            property['path'] = p['path']
587            property['type'] = p['type'].lower()
588            property['value'] = str(p['value']).lower()
589            properties.append(property)
590        condition['properties'] = properties
591
592        return condition
593
594
595def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
596    """
597    Combines the zone definition YAML and fan
598    definition YAML to create a data structure defining
599    the fan cooling zones.
600    """
601
602    zone_groups = []
603
604    for group in zone_data:
605        conditions = []
606        # zone conditions are optional
607        if 'zone_conditions' in group and group['zone_conditions'] is not None:
608            for c in group['zone_conditions']:
609
610                if not zone_conditions_data:
611                    sys.exit("No zone_conditions YAML file but " +
612                             "zone_conditions used in zone YAML")
613
614                condition = getConditionInZoneConditions(c['name'],
615                                                         zone_conditions_data)
616
617                if not condition:
618                    sys.exit("Missing zone condition " + c['name'])
619
620                conditions.append(condition)
621
622        zone_group = {}
623        zone_group['conditions'] = conditions
624
625        zones = []
626        for z in group['zones']:
627            zone = {}
628
629            # 'zone' is required
630            if ('zone' not in z) or (z['zone'] is None):
631                sys.exit("Missing fan zone number in " + zone_yaml)
632
633            zone['num'] = z['zone']
634
635            zone['full_speed'] = z['full_speed']
636
637            zone['default_floor'] = z['default_floor']
638
639            # 'increase_delay' is optional (use 0 by default)
640            key = 'increase_delay'
641            zone[key] = z.setdefault(key, 0)
642
643            # 'decrease_interval' is optional (use 0 by default)
644            key = 'decrease_interval'
645            zone[key] = z.setdefault(key, 0)
646
647            # 'cooling_profiles' is optional (use 'all' instead)
648            if ('cooling_profiles' not in z) or \
649                    (z['cooling_profiles'] is None):
650                profiles = ["all"]
651            else:
652                profiles = z['cooling_profiles']
653
654            fans = getFansInZone(z['zone'], profiles, fan_data)
655            events = getEventsInZone(z['zone'], group['zone_conditions'],
656                                     events_data)
657
658            if len(fans) == 0:
659                sys.exit("Didn't find any fans in zone " + str(zone['num']))
660
661            zone['fans'] = fans
662            zone['events'] = events
663            zones.append(zone)
664
665        zone_group['zones'] = zones
666        zone_groups.append(zone_group)
667
668    return zone_groups
669
670
671if __name__ == '__main__':
672    parser = ArgumentParser(
673        description="Phosphor fan zone definition parser")
674
675    parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
676                        default="example/zones.yaml",
677                        help='fan zone definitional yaml')
678    parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
679                        default="example/fans.yaml",
680                        help='fan definitional yaml')
681    parser.add_argument('-e', '--events_yaml', dest='events_yaml',
682                        help='events to set speeds yaml')
683    parser.add_argument('-c', '--zone_conditions_yaml',
684                        dest='zone_conditions_yaml',
685                        help='conditions to determine zone yaml')
686    parser.add_argument('-o', '--output_dir', dest='output_dir',
687                        default=".",
688                        help='output directory')
689    args = parser.parse_args()
690
691    if not args.zone_yaml or not args.fan_yaml:
692        parser.print_usage()
693        sys.exit(1)
694
695    with open(args.zone_yaml, 'r') as zone_input:
696        zone_data = yaml.safe_load(zone_input) or {}
697
698    with open(args.fan_yaml, 'r') as fan_input:
699        fan_data = yaml.safe_load(fan_input) or {}
700
701    events_data = {}
702    if args.events_yaml:
703        with open(args.events_yaml, 'r') as events_input:
704            events_data = yaml.safe_load(events_input) or {}
705
706    zone_conditions_data = {}
707    if args.zone_conditions_yaml:
708        with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
709            zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
710
711    zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
712                                fan_data, events_data, zone_conditions_data)
713
714    manager_config = zone_data.get('manager_configuration', {})
715
716    if manager_config.get('power_on_delay') is None:
717        manager_config['power_on_delay'] = 0
718
719    tmpls_dir = os.path.join(
720        os.path.dirname(os.path.realpath(__file__)),
721        "templates")
722    output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
723    if sys.version_info < (3, 0):
724        lkup = TemplateLookup(
725            directories=tmpls_dir.split(),
726            disable_unicode=True)
727    else:
728        lkup = TemplateLookup(
729            directories=tmpls_dir.split())
730    tmpl = lkup.get_template('fan_zone_defs.mako.cpp')
731    with open(output_file, 'w') as output:
732        output.write(tmpl.render(zones=zone_config,
733                                 mgr_data=manager_config))
734