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