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