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