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