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