xref: /openbmc/phosphor-fan-presence/control/gen-fan-zone-defs.py (revision 7b7ceb8deb48d6d5833b02bc33987fca2e647a68)
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    static_cast<${p['type']}>(${p['value']}),
64%else:
65    static_cast<${p['type']}>(${p['value']}))
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 = {}
299                if type(eActions[p]) is not dict:
300                    if p == 'property':
301                        param['value'] = str(eActions[p]).lower()
302                        param['type'] = str(
303                            e['property']['type']).lower()
304                    else:
305                        # Default type to 'size_t' when not given
306                        param['value'] = str(eActions[p]).lower()
307                        param['type'] = 'size_t'
308                    params.append(param)
309                else:
310                    param['type'] = str(eActions[p]['type']).lower()
311                    if p != 'map':
312                        param['value'] = str(
313                            eActions[p]['value']).lower()
314                    else:
315                        emap = convertToMap(str(eActions[p]['value']))
316                        param['value'] = param['type'] + emap
317                    params.append(param)
318        actions['parameters'] = params
319        action.append(actions)
320    event['action'] = action
321
322    # Add signal handlers
323    signals = []
324    for member in group:
325        for eMatches in e['matches']:
326            signal = {}
327            eMatch = next(m for m in events_data['matches']
328                          if m['name'] == eMatches['name'])
329            signal['match'] = eMatch['name']
330            params = []
331            if ('parameters' in eMatch) and \
332               (eMatch['parameters'] is not None):
333                for p in eMatch['parameters']:
334                    params.append(member[str(p)])
335            signal['mparams'] = params
336            eSignal = next(s for s in events_data['signals']
337                           if s['name'] == eMatch['signal'])
338            signal['signal'] = eSignal['name']
339            sparams = {}
340            if ('parameters' in eSignal) and \
341               (eSignal['parameters'] is not None):
342                splist = []
343                for p in eSignal['parameters']:
344                    sp = str(p)
345                    if (sp != 'type'):
346                        splist.append(sp)
347                        if (sp != 'group'):
348                            sparams[sp] = "\"" + member[sp] + "\""
349                        else:
350                            sparams[sp] = "Group{\n"
351                            for m in group:
352                                sparams[sp] += (
353                                    "{\n" +
354                                    "\"" + str(m['object']) + "\",\n" +
355                                    "{\"" + str(m['interface']) + "\"," +
356                                    "\"" + str(m['property']) + "\"}\n" +
357                                    "},\n")
358                            sparams[sp] += "}"
359                    else:
360                        sparams[sp] = member[sp]
361                sparams['params'] = splist
362            signal['sparams'] = sparams
363            # Add signal handler
364            eHandler = next(h for h in events_data['handlers']
365                            if h['name'] == eSignal['handler'])
366            signal['handler'] = eHandler['name']
367            hparams = {}
368            if ('parameters' in eHandler) and \
369               (eHandler['parameters'] is not None):
370                hplist = []
371                for p in eHandler['parameters']:
372                    hp = str(p)
373                    if (hp != 'type'):
374                        hplist.append(hp)
375                        if (hp != 'group'):
376                            hparams[hp] = "\"" + member[hp] + "\""
377                        else:
378                            hparams[hp] = "Group{\n"
379                            for m in group:
380                                hparams[hp] += (
381                                    "{\n" +
382                                    "\"" + str(m['object']) + "\",\n" +
383                                    "{\"" + str(m['interface']) + "\"," +
384                                    "\"" + str(m['property']) + "\"}\n" +
385                                    "},\n")
386                            hparams[hp] += "}"
387                    else:
388                        hparams[hp] = member[hp]
389                hparams['params'] = hplist
390            signal['hparams'] = hparams
391            signals.append(signal)
392    event['signals'] = signals
393
394    # Add optional action call timer
395    timer = {}
396    interval = "static_cast<std::chrono::seconds>"
397    if ('timer' in e) and \
398       (e['timer'] is not None):
399        timer['interval'] = (interval +
400                             "(" +
401                             str(e['timer']['interval']) +
402                             ")")
403    else:
404        timer['interval'] = (interval +
405                             "(" + str(0) + ")")
406    timer['type'] = "util::Timer::TimerType::repeating"
407    event['timer'] = timer
408
409    return event
410
411
412def addPrecondition(zNum, zCond, event, events_data):
413    """
414    Parses the precondition section of an event and populates the necessary
415    structures to generate a precondition for a set speed event.
416    """
417    precond = {}
418    # Add set speed event precondition group
419    group = []
420    for grp in event['precondition']['groups']:
421        groups = next(g for g in events_data['groups']
422                      if g['name'] == grp['name'])
423        for member in groups['members']:
424            members = {}
425            members['object'] = (groups['type'] +
426                                 member)
427            members['interface'] = grp['interface']
428            members['property'] = grp['property']['name']
429            members['type'] = grp['property']['type']
430            members['value'] = grp['property']['value']
431            group.append(members)
432    precond['pcgrp'] = group
433
434    # Add set speed event precondition actions
435    pc = []
436    pcs = {}
437    pcs['name'] = event['precondition']['name']
438    epc = next(p for p in events_data['preconditions']
439               if p['name'] == event['precondition']['name'])
440    params = []
441    for p in epc['parameters']:
442        param = {}
443        if p == 'groups':
444            param['type'] = "std::vector<PrecondGroup>"
445            param['open'] = "{"
446            param['close'] = "}"
447            values = []
448            for pcgrp in group:
449                value = {}
450                value['value'] = (
451                    "PrecondGroup{\"" +
452                    str(pcgrp['object']) + "\",\"" +
453                    str(pcgrp['interface']) + "\",\"" +
454                    str(pcgrp['property']) + "\"," +
455                    "static_cast<" +
456                    str(pcgrp['type']).lower() + ">" +
457                    "(" + str(pcgrp['value']).lower() + ")}")
458                values.append(value)
459            param['values'] = values
460        params.append(param)
461    pcs['params'] = params
462    pc.append(pcs)
463    precond['pcact'] = pc
464
465    pcevents = []
466    for pce in event['precondition']['events']:
467        pcevent = getEvent(zNum, zCond, pce, events_data)
468        if not pcevent:
469            continue
470        pcevents.append(pcevent)
471    precond['pcevts'] = pcevents
472
473    # Add precondition signal handlers
474    signals = []
475    for member in group:
476        for eMatches in event['precondition']['matches']:
477            signal = {}
478            eMatch = next(m for m in events_data['matches']
479                          if m['name'] == eMatches['name'])
480            signal['match'] = eMatch['name']
481            params = []
482            if ('parameters' in eMatch) and \
483               (eMatch['parameters'] is not None):
484                for p in eMatch['parameters']:
485                    params.append(member[str(p)])
486            signal['mparams'] = params
487            eSignal = next(s for s in events_data['signals']
488                           if s['name'] == eMatch['signal'])
489            signal['signal'] = eSignal['name']
490            sparams = {}
491            if ('parameters' in eSignal) and \
492               (eSignal['parameters'] is not None):
493                splist = []
494                for p in eSignal['parameters']:
495                    sp = str(p)
496                    if (sp != 'type'):
497                        splist.append(sp)
498                        if (sp != 'group'):
499                            sparams[sp] = "\"" + member[sp] + "\""
500                        else:
501                            sparams[sp] = "Group{\n"
502                            for m in group:
503                                sparams[sp] += (
504                                    "{\n" +
505                                    "\"" + str(m['object']) + "\",\n" +
506                                    "{\"" + str(m['interface']) + "\"," +
507                                    "\"" + str(m['property']) + "\"}\n" +
508                                    "},\n")
509                            sparams[sp] += "}"
510                    else:
511                        sparams[sp] = member[sp]
512                sparams['params'] = splist
513            signal['sparams'] = sparams
514            # Add signal handler
515            eHandler = next(h for h in events_data['handlers']
516                            if h['name'] == eSignal['handler'])
517            signal['handler'] = eHandler['name']
518            hparams = {}
519            if ('parameters' in eHandler) and \
520               (eHandler['parameters'] is not None):
521                hplist = []
522                for p in eHandler['parameters']:
523                    hp = str(p)
524                    if (hp != 'type'):
525                        hplist.append(hp)
526                        if (hp != 'group'):
527                            hparams[hp] = "\"" + member[hp] + "\""
528                        else:
529                            hparams[hp] = "Group{\n"
530                            for m in group:
531                                hparams[hp] += (
532                                    "{\n" +
533                                    "\"" + str(m['object']) + "\",\n" +
534                                    "{\"" + str(m['interface']) + "\"," +
535                                    "\"" + str(m['property']) + "\"}\n" +
536                                    "},\n")
537                            hparams[hp] += "}"
538                    else:
539                        hparams[hp] = member[hp]
540                hparams['params'] = hplist
541            signal['hparams'] = hparams
542            signals.append(signal)
543    precond['pcsigs'] = signals
544
545    # Add optional action call timer
546    timer = {}
547    interval = "static_cast<std::chrono::seconds>"
548    if ('timer' in event['precondition']) and \
549       (event['precondition']['timer'] is not None):
550        timer['interval'] = (interval +
551                             "(" +
552                             str(event['precondition']['timer']['interval']) +
553                             ")")
554    else:
555        timer['interval'] = (interval +
556                             "(" + str(0) + ")")
557    timer['type'] = "util::Timer::TimerType::repeating"
558    precond['pctime'] = timer
559
560    return precond
561
562
563def getEventsInZone(zone_num, zone_conditions, events_data):
564    """
565    Constructs the event entries defined for each zone using the events yaml
566    provided.
567    """
568    events = []
569
570    if 'events' in events_data:
571        for e in events_data['events']:
572            event = {}
573            # Add precondition if given
574            if ('precondition' in e) and \
575               (e['precondition'] is not None):
576                event['pc'] = addPrecondition(zone_num,
577                                              zone_conditions,
578                                              e,
579                                              events_data)
580            else:
581                event = getEvent(zone_num, zone_conditions, e, events_data)
582                if not event:
583                    continue
584            events.append(event)
585
586    return events
587
588
589def getFansInZone(zone_num, profiles, fan_data):
590    """
591    Parses the fan definition YAML files to find the fans
592    that match both the zone passed in and one of the
593    cooling profiles.
594    """
595
596    fans = []
597
598    for f in fan_data['fans']:
599
600        if zone_num != f['cooling_zone']:
601            continue
602
603        # 'cooling_profile' is optional (use 'all' instead)
604        if f.get('cooling_profile') is None:
605            profile = "all"
606        else:
607            profile = f['cooling_profile']
608
609        if profile not in profiles:
610            continue
611
612        fan = {}
613        fan['name'] = f['inventory']
614        fan['sensors'] = f['sensors']
615        fans.append(fan)
616
617    return fans
618
619
620def getConditionInZoneConditions(zone_condition, zone_conditions_data):
621    """
622    Parses the zone conditions definition YAML files to find the condition
623    that match both the zone condition passed in.
624    """
625
626    condition = {}
627
628    for c in zone_conditions_data['conditions']:
629
630        if zone_condition != c['name']:
631            continue
632        condition['type'] = c['type']
633        properties = []
634        for p in c['properties']:
635            property = {}
636            property['property'] = p['property']
637            property['interface'] = p['interface']
638            property['path'] = p['path']
639            property['type'] = p['type'].lower()
640            property['value'] = str(p['value']).lower()
641            properties.append(property)
642        condition['properties'] = properties
643
644        return condition
645
646
647def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
648    """
649    Combines the zone definition YAML and fan
650    definition YAML to create a data structure defining
651    the fan cooling zones.
652    """
653
654    zone_groups = []
655
656    for group in zone_data:
657        conditions = []
658        # zone conditions are optional
659        if 'zone_conditions' in group and group['zone_conditions'] is not None:
660            for c in group['zone_conditions']:
661
662                if not zone_conditions_data:
663                    sys.exit("No zone_conditions YAML file but " +
664                             "zone_conditions used in zone YAML")
665
666                condition = getConditionInZoneConditions(c['name'],
667                                                         zone_conditions_data)
668
669                if not condition:
670                    sys.exit("Missing zone condition " + c['name'])
671
672                conditions.append(condition)
673
674        zone_group = {}
675        zone_group['conditions'] = conditions
676
677        zones = []
678        for z in group['zones']:
679            zone = {}
680
681            # 'zone' is required
682            if ('zone' not in z) or (z['zone'] is None):
683                sys.exit("Missing fan zone number in " + zone_yaml)
684
685            zone['num'] = z['zone']
686
687            zone['full_speed'] = z['full_speed']
688
689            zone['default_floor'] = z['default_floor']
690
691            # 'increase_delay' is optional (use 0 by default)
692            key = 'increase_delay'
693            zone[key] = z.setdefault(key, 0)
694
695            # 'decrease_interval' is optional (use 0 by default)
696            key = 'decrease_interval'
697            zone[key] = z.setdefault(key, 0)
698
699            # 'cooling_profiles' is optional (use 'all' instead)
700            if ('cooling_profiles' not in z) or \
701                    (z['cooling_profiles'] is None):
702                profiles = ["all"]
703            else:
704                profiles = z['cooling_profiles']
705
706            fans = getFansInZone(z['zone'], profiles, fan_data)
707            events = getEventsInZone(z['zone'], group['zone_conditions'],
708                                     events_data)
709
710            if len(fans) == 0:
711                sys.exit("Didn't find any fans in zone " + str(zone['num']))
712
713            zone['fans'] = fans
714            zone['events'] = events
715            zones.append(zone)
716
717        zone_group['zones'] = zones
718        zone_groups.append(zone_group)
719
720    return zone_groups
721
722
723if __name__ == '__main__':
724    parser = ArgumentParser(
725        description="Phosphor fan zone definition parser")
726
727    parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
728                        default="example/zones.yaml",
729                        help='fan zone definitional yaml')
730    parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
731                        default="example/fans.yaml",
732                        help='fan definitional yaml')
733    parser.add_argument('-e', '--events_yaml', dest='events_yaml',
734                        help='events to set speeds yaml')
735    parser.add_argument('-c', '--zone_conditions_yaml',
736                        dest='zone_conditions_yaml',
737                        help='conditions to determine zone yaml')
738    parser.add_argument('-o', '--output_dir', dest='output_dir',
739                        default=".",
740                        help='output directory')
741    args = parser.parse_args()
742
743    if not args.zone_yaml or not args.fan_yaml:
744        parser.print_usage()
745        sys.exit(-1)
746
747    with open(args.zone_yaml, 'r') as zone_input:
748        zone_data = yaml.safe_load(zone_input) or {}
749
750    with open(args.fan_yaml, 'r') as fan_input:
751        fan_data = yaml.safe_load(fan_input) or {}
752
753    events_data = {}
754    if args.events_yaml:
755        with open(args.events_yaml, 'r') as events_input:
756            events_data = yaml.safe_load(events_input) or {}
757
758    zone_conditions_data = {}
759    if args.zone_conditions_yaml:
760        with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
761            zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
762
763    zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
764                                fan_data, events_data, zone_conditions_data)
765
766    manager_config = zone_data.get('manager_configuration', {})
767
768    if manager_config.get('power_on_delay') is None:
769        manager_config['power_on_delay'] = 0
770
771    output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
772    with open(output_file, 'w') as output:
773        output.write(Template(tmpl).render(zones=zone_config,
774                     mgr_data=manager_config))
775