blob: 45ca77468f7b58486ab22a826f1b57bc400ec55a [file] [log] [blame]
Matt Spinlerd08dbe22017-04-11 13:52:54 -05001#!/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
Matt Spinlerd08dbe22017-04-11 13:52:54 -050014tmpl = '''/* This is a generated file. */
Matthew Barth34f1bda2017-05-31 13:45:36 -050015#include <sdbusplus/bus.hpp>
Matt Spinlerd08dbe22017-04-11 13:52:54 -050016#include "manager.hpp"
Matthew Barthba102b32017-05-16 16:13:56 -050017#include "functor.hpp"
18#include "actions.hpp"
Matthew Barth7e527c12017-05-17 10:14:15 -050019#include "handlers.hpp"
Matthew Barth9af190c2017-08-08 14:20:43 -050020#include "preconditions.hpp"
Matt Spinlerd08dbe22017-04-11 13:52:54 -050021
22using namespace phosphor::fan::control;
Matthew Barth34f1bda2017-05-31 13:45:36 -050023using namespace sdbusplus::bus::match::rules;
Matt Spinlerd08dbe22017-04-11 13:52:54 -050024
Matt Spinleree7f6422017-05-09 11:03:14 -050025const unsigned int Manager::_powerOnDelay{${mgr_data['power_on_delay']}};
26
Matt Spinlerd08dbe22017-04-11 13:52:54 -050027const std::vector<ZoneGroup> Manager::_zoneLayouts
28{
29%for zone_group in zones:
Matthew Barth9c726672017-05-18 13:44:46 -050030 ZoneGroup{
Gunnar Millsee8a2812017-06-02 14:26:47 -050031 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 },
Matthew Barth9c726672017-05-18 13:44:46 -050048 std::vector<ZoneDefinition>{
49 %for zone in zone_group['zones']:
50 ZoneDefinition{
51 ${zone['num']},
52 ${zone['full_speed']},
Matthew Barth1de66622017-06-12 13:13:02 -050053 ${zone['default_floor']},
Matthew Bartha9561842017-06-29 11:43:45 -050054 ${zone['increase_delay']},
55 ${zone['decrease_interval']},
Matthew Barth9c726672017-05-18 13:44:46 -050056 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']:
Matthew Barth9af190c2017-08-08 14:20:43 -050070 %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
Matthew Barth9c726672017-05-18 13:44:46 -050096 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{
Matthew Barthbe250832017-08-11 13:16:28 -0500118 interfacesAdded("${s['obj_path']}"),
119 make_handler(objectSignal<${s['type']}>(
120 "${s['path']}",
121 "${s['interface']}",
122 "${s['property']}",
123 handler::setProperty<${s['type']}>(
124 "${s['path']}",
125 "${s['interface']}",
126 "${s['property']}"
127 )
128 ))
129 },
130 PropertyChange{
131 propertiesChanged(
132 "${s['path']}",
133 "${s['interface']}"),
Matthew Barth9c726672017-05-18 13:44:46 -0500134 make_handler(propertySignal<${s['type']}>(
135 "${s['interface']}",
136 "${s['property']}",
137 handler::setProperty<${s['type']}>(
Matthew Barthbe250832017-08-11 13:16:28 -0500138 "${s['path']}",
Matthew Barthcec5ab72017-06-02 15:20:56 -0500139 "${s['interface']}",
Matthew Barth9c726672017-05-18 13:44:46 -0500140 "${s['property']}"
141 )
142 ))
143 },
144 %endfor
145 }
Matthew Barth9af190c2017-08-08 14:20:43 -0500146 %if ('pc' in event) and (event['pc'] is not None):
147 }
148 )),
149 std::vector<PropertyChange>{
150 %for s in event['pc']['pcsig']:
151 PropertyChange{
152 interfacesAdded("${s['obj_path']}"),
153 make_handler(objectSignal<${s['type']}>(
154 "${s['path']}",
155 "${s['interface']}",
156 "${s['property']}",
157 handler::setProperty<${s['type']}>(
158 "${s['path']}",
159 "${s['interface']}",
160 "${s['property']}"
161 )
162 ))
163 },
164 PropertyChange{
Matthew Barthbe250832017-08-11 13:16:28 -0500165 propertiesChanged(
166 "${s['path']}",
167 "${s['interface']}"),
Matthew Barth9af190c2017-08-08 14:20:43 -0500168 make_handler(propertySignal<${s['type']}>(
169 "${s['interface']}",
170 "${s['property']}",
171 handler::setProperty<${s['type']}>(
172 "${s['path']}",
173 "${s['interface']}",
174 "${s['property']}"
175 )
176 ))
177 },
178 %endfor
179 }
180 %endif
Matthew Barth9c726672017-05-18 13:44:46 -0500181 },
182 %endfor
183 }
184 },
185 %endfor
186 }
Matt Spinler78498c92017-04-11 13:59:46 -0500187 },
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500188%endfor
189};
190'''
191
Matt Spinler78498c92017-04-11 13:59:46 -0500192
Matthew Barthbb12c922017-06-13 13:57:40 -0500193def convertToMap(listOfDict):
194 """
195 Converts a list of dictionary entries to a std::map initialization list.
196 """
197 listOfDict = listOfDict.replace('[', '{')
198 listOfDict = listOfDict.replace(']', '}')
199 listOfDict = listOfDict.replace(':', ',')
200 return listOfDict
201
202
Matthew Barth9af190c2017-08-08 14:20:43 -0500203def addPrecondition(event, events_data):
204 """
205 Parses the precondition section of an event and populates the necessary
206 structures to generate a precondition for a set speed event.
207 """
208 precond = {}
209 # Add set speed event precondition group
210 group = []
211 for grp in event['precondition']['groups']:
212 groups = next(g for g in events_data['groups']
213 if g['name'] == grp['name'])
214 for member in groups['members']:
215 members = {}
216 members['obj_path'] = groups['type']
217 members['name'] = (groups['type'] +
218 member)
219 members['interface'] = grp['interface']
220 members['property'] = grp['property']['name']
221 members['type'] = grp['property']['type']
222 members['value'] = grp['property']['value']
223 group.append(members)
224 precond['pcgrp'] = group
225
226 # Add set speed event precondition action
227 pc = {}
228 pc['name'] = event['precondition']['name']
229 pcs = next(p for p in events_data['preconditions']
230 if p['name'] == event['precondition']['name'])
231 params = []
232 for p in pcs['parameters']:
233 param = {}
234 if p == 'groups':
235 param['type'] = "std::vector<PrecondGroup>"
236 param['open'] = "{"
237 param['close'] = "}"
238 values = []
239 for pcgrp in group:
240 value = {}
241 value['value'] = (
242 "PrecondGroup{\"" +
243 str(pcgrp['name']) + "\",\"" +
244 str(pcgrp['interface']) + "\",\"" +
245 str(pcgrp['property']) + "\"," +
246 "static_cast<" +
247 str(pcgrp['type']).lower() + ">" +
248 "(" + str(pcgrp['value']).lower() + ")}")
249 values.append(value)
250 param['values'] = values
251 params.append(param)
252 pc['params'] = params
253 precond['pcact'] = pc
254
255 # Add precondition property change signal handler
256 signal = []
257 for member in group:
258 signals = {}
259 signals['obj_path'] = member['obj_path']
260 signals['path'] = member['name']
261 signals['interface'] = member['interface']
262 signals['property'] = member['property']
263 signals['type'] = member['type']
264 signal.append(signals)
265 precond['pcsig'] = signal
266
267 return precond
268
269
Gunnar Millsb751f322017-06-06 15:14:11 -0500270def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500271 """
272 Constructs the event entries defined for each zone using the events yaml
273 provided.
274 """
275 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500276
Matthew Barthd4d0f082017-05-16 13:51:10 -0500277 if 'events' in events_data:
278 for e in events_data['events']:
Gunnar Millsb751f322017-06-06 15:14:11 -0500279
280 # Zone numbers are optional in the events yaml but skip if this
281 # zone's zone number is not in the event's zone numbers
282 if all('zones' in z and z['zones'] is not None and
283 zone_num not in z['zones'] for z in e['zone_conditions']):
284 continue
285
286 # Zone conditions are optional in the events yaml but skip if this
287 # event's condition is not in this zone's conditions
288 if all('name' in z and z['name'] is not None and
289 not any(c['name'] == z['name'] for c in zone_conditions)
290 for z in e['zone_conditions']):
291 continue
Matthew Barthd4d0f082017-05-16 13:51:10 -0500292
293 event = {}
Matthew Barth9af190c2017-08-08 14:20:43 -0500294 # Add precondition if given
295 if ('precondition' in e) and \
296 (e['precondition'] is not None):
297 event['pc'] = addPrecondition(e, events_data)
298
Matthew Barthd4d0f082017-05-16 13:51:10 -0500299 # Add set speed event group
300 group = []
301 groups = next(g for g in events_data['groups']
302 if g['name'] == e['group'])
303 for member in groups['members']:
304 members = {}
Matthew Barthbe250832017-08-11 13:16:28 -0500305 members['obj_path'] = groups['type']
Matthew Barth9af190c2017-08-08 14:20:43 -0500306 members['name'] = (groups['type'] +
Matthew Barthd4d0f082017-05-16 13:51:10 -0500307 member)
308 members['interface'] = e['interface']
309 members['property'] = e['property']['name']
310 group.append(members)
311 event['group'] = group
Matthew Barthba102b32017-05-16 16:13:56 -0500312
313 # Add set speed action and function parameters
314 action = {}
315 actions = next(a for a in events_data['actions']
Matthew Barth9c726672017-05-18 13:44:46 -0500316 if a['name'] == e['action']['name'])
Matthew Barthba102b32017-05-16 16:13:56 -0500317 action['name'] = actions['name']
318 params = []
319 for p in actions['parameters']:
320 param = {}
321 if type(e['action'][p]) is not dict:
322 if p == 'property':
323 param['value'] = str(e['action'][p]).lower()
324 param['type'] = str(e['property']['type']).lower()
325 else:
326 # Default type to 'size_t' when not given
327 param['value'] = str(e['action'][p]).lower()
328 param['type'] = 'size_t'
329 params.append(param)
330 else:
Matthew Barthba102b32017-05-16 16:13:56 -0500331 param['type'] = str(e['action'][p]['type']).lower()
Matthew Barthbb12c922017-06-13 13:57:40 -0500332 if p != 'map':
333 param['value'] = str(e['action'][p]['value']).lower()
334 else:
335 emap = convertToMap(str(e['action'][p]['value']))
336 param['value'] = param['type'] + emap
Matthew Barthba102b32017-05-16 16:13:56 -0500337 params.append(param)
338 action['parameters'] = params
339 event['action'] = action
340
Matthew Barth7e527c12017-05-17 10:14:15 -0500341 # Add property change signal handler
342 signal = []
343 for path in group:
344 signals = {}
Matthew Barthbe250832017-08-11 13:16:28 -0500345 signals['obj_path'] = path['obj_path']
Matthew Barth7e527c12017-05-17 10:14:15 -0500346 signals['path'] = path['name']
347 signals['interface'] = e['interface']
348 signals['property'] = e['property']['name']
349 signals['type'] = e['property']['type']
Matthew Barth7e527c12017-05-17 10:14:15 -0500350 signal.append(signals)
351 event['signal'] = signal
352
Matthew Barthd4d0f082017-05-16 13:51:10 -0500353 events.append(event)
354
355 return events
356
357
Matt Spinler78498c92017-04-11 13:59:46 -0500358def getFansInZone(zone_num, profiles, fan_data):
359 """
360 Parses the fan definition YAML files to find the fans
361 that match both the zone passed in and one of the
362 cooling profiles.
363 """
364
365 fans = []
366
367 for f in fan_data['fans']:
368
369 if zone_num != f['cooling_zone']:
370 continue
371
Gunnar Mills67e95512017-06-02 14:35:18 -0500372 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500373 if f.get('cooling_profile') is None:
374 profile = "all"
375 else:
376 profile = f['cooling_profile']
377
378 if profile not in profiles:
379 continue
380
381 fan = {}
382 fan['name'] = f['inventory']
383 fan['sensors'] = f['sensors']
384 fans.append(fan)
385
386 return fans
387
388
Gunnar Millsee8a2812017-06-02 14:26:47 -0500389def getConditionInZoneConditions(zone_condition, zone_conditions_data):
390 """
391 Parses the zone conditions definition YAML files to find the condition
392 that match both the zone condition passed in.
393 """
394
395 condition = {}
396
397 for c in zone_conditions_data['conditions']:
398
399 if zone_condition != c['name']:
400 continue
401 condition['type'] = c['type']
402 properties = []
403 for p in c['properties']:
404 property = {}
405 property['property'] = p['property']
406 property['interface'] = p['interface']
407 property['path'] = p['path']
408 property['type'] = p['type'].lower()
409 property['value'] = str(p['value']).lower()
410 properties.append(property)
411 condition['properties'] = properties
412
413 return condition
414
415
416def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
Matt Spinler78498c92017-04-11 13:59:46 -0500417 """
418 Combines the zone definition YAML and fan
419 definition YAML to create a data structure defining
420 the fan cooling zones.
421 """
422
423 zone_groups = []
424
425 for group in zone_data:
426 conditions = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500427 # zone conditions are optional
428 if 'zone_conditions' in group and group['zone_conditions'] is not None:
429 for c in group['zone_conditions']:
430
431 if not zone_conditions_data:
Gunnar Millsb751f322017-06-06 15:14:11 -0500432 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500433 "zone_conditions used in zone YAML")
434
435 condition = getConditionInZoneConditions(c['name'],
436 zone_conditions_data)
437
438 if not condition:
439 sys.exit("Missing zone condition " + c['name'])
440
441 conditions.append(condition)
Matt Spinler78498c92017-04-11 13:59:46 -0500442
443 zone_group = {}
444 zone_group['conditions'] = conditions
445
446 zones = []
447 for z in group['zones']:
448 zone = {}
449
Gunnar Mills67e95512017-06-02 14:35:18 -0500450 # 'zone' is required
451 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500452 sys.exit("Missing fan zone number in " + zone_yaml)
453
454 zone['num'] = z['zone']
455
456 zone['full_speed'] = z['full_speed']
457
Matthew Barth1de66622017-06-12 13:13:02 -0500458 zone['default_floor'] = z['default_floor']
459
Matthew Bartha9561842017-06-29 11:43:45 -0500460 # 'increase_delay' is optional (use 0 by default)
461 key = 'increase_delay'
462 zone[key] = z.setdefault(key, 0)
463
464 # 'decrease_interval' is optional (use 0 by default)
465 key = 'decrease_interval'
466 zone[key] = z.setdefault(key, 0)
467
Gunnar Mills67e95512017-06-02 14:35:18 -0500468 # 'cooling_profiles' is optional (use 'all' instead)
469 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500470 (z['cooling_profiles'] is None):
471 profiles = ["all"]
472 else:
473 profiles = z['cooling_profiles']
474
475 fans = getFansInZone(z['zone'], profiles, fan_data)
Gunnar Millsb751f322017-06-06 15:14:11 -0500476 events = getEventsInZone(z['zone'], group['zone_conditions'],
477 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500478
479 if len(fans) == 0:
480 sys.exit("Didn't find any fans in zone " + str(zone['num']))
481
482 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500483 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500484 zones.append(zone)
485
486 zone_group['zones'] = zones
487 zone_groups.append(zone_group)
488
489 return zone_groups
490
491
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500492if __name__ == '__main__':
493 parser = ArgumentParser(
494 description="Phosphor fan zone definition parser")
495
496 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
497 default="example/zones.yaml",
498 help='fan zone definitional yaml')
499 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
500 default="example/fans.yaml",
501 help='fan definitional yaml')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500502 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
503 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500504 parser.add_argument('-c', '--zone_conditions_yaml',
505 dest='zone_conditions_yaml',
506 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500507 parser.add_argument('-o', '--output_dir', dest='output_dir',
508 default=".",
509 help='output directory')
510 args = parser.parse_args()
511
512 if not args.zone_yaml or not args.fan_yaml:
513 parser.print_usage()
514 sys.exit(-1)
515
516 with open(args.zone_yaml, 'r') as zone_input:
517 zone_data = yaml.safe_load(zone_input) or {}
518
519 with open(args.fan_yaml, 'r') as fan_input:
520 fan_data = yaml.safe_load(fan_input) or {}
521
Matthew Barthd4d0f082017-05-16 13:51:10 -0500522 events_data = {}
523 if args.events_yaml:
524 with open(args.events_yaml, 'r') as events_input:
525 events_data = yaml.safe_load(events_input) or {}
526
Gunnar Millsee8a2812017-06-02 14:26:47 -0500527 zone_conditions_data = {}
528 if args.zone_conditions_yaml:
529 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
530 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
531
Matt Spinleree7f6422017-05-09 11:03:14 -0500532 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500533 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500534
535 manager_config = zone_data.get('manager_configuration', {})
536
537 if manager_config.get('power_on_delay') is None:
538 manager_config['power_on_delay'] = 0
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500539
540 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
541 with open(output_file, 'w') as output:
Matt Spinleree7f6422017-05-09 11:03:14 -0500542 output.write(Template(tmpl).render(zones=zone_config,
543 mgr_data=manager_config))