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