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