xref: /openbmc/phosphor-fan-presence/control/gen-fan-zone-defs.py (revision 4c04a016faa989bbc1ad4334b164535f017954b1)
1 #!/usr/bin/env python
2 
3 """
4 This script reads in fan definition and zone definition YAML
5 files and generates a set of structures for use by the fan control code.
6 """
7 
8 import os
9 import sys
10 import yaml
11 from argparse import ArgumentParser
12 from mako.template import Template
13 
14 tmpl = '''
15 <%!
16 def indent(str, depth):
17     return ''.join(4*' '*depth+line for line in str.splitlines(True))
18 %>
19 <%def name="genSSE(event)" buffered="True">
20 Group{
21 %for member in event['group']:
22 {
23     "${member['name']}",
24     {"${member['interface']}",
25      "${member['property']}"}
26 },
27 %endfor
28 },
29 std::vector<Action>{
30 %for a in event['action']:
31 %if len(a['parameters']) != 0:
32 make_action(action::${a['name']}(
33 %else:
34 make_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 },
46 Timer{
47     ${event['timer']['interval']}
48 },
49 std::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 
89 using namespace phosphor::fan::control;
90 using namespace sdbusplus::bus::match::rules;
91 
92 const unsigned int Manager::_powerOnDelay{${mgr_data['power_on_delay']}};
93 
94 const 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 
242 def 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 
252 def 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 
351 def 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 
442 def 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 
468 def 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 
499 def 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 
526 def 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 
602 if __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