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