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