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