blob: a2a49a8f7dbadbf823471125054931c01291aa15 [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 )),
Matthew Barth90149802017-08-15 10:51:37 -0500115 Timer{
116 ${event['timer']['interval']}
117 },
Matthew Barth9c726672017-05-18 13:44:46 -0500118 std::vector<PropertyChange>{
119 %for s in event['signal']:
120 PropertyChange{
Matthew Barthbe250832017-08-11 13:16:28 -0500121 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']}"),
Matthew Barth9c726672017-05-18 13:44:46 -0500137 make_handler(propertySignal<${s['type']}>(
138 "${s['interface']}",
139 "${s['property']}",
140 handler::setProperty<${s['type']}>(
Matthew Barthbe250832017-08-11 13:16:28 -0500141 "${s['path']}",
Matthew Barthcec5ab72017-06-02 15:20:56 -0500142 "${s['interface']}",
Matthew Barth9c726672017-05-18 13:44:46 -0500143 "${s['property']}"
144 )
145 ))
146 },
147 %endfor
148 }
Matthew Barth9af190c2017-08-08 14:20:43 -0500149 %if ('pc' in event) and (event['pc'] is not None):
150 }
151 )),
Matthew Barth90149802017-08-15 10:51:37 -0500152 Timer{
153 ${event['pc']['pctime']['interval']}
154 },
Matthew Barth9af190c2017-08-08 14:20:43 -0500155 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{
Matthew Barthbe250832017-08-11 13:16:28 -0500171 propertiesChanged(
172 "${s['path']}",
173 "${s['interface']}"),
Matthew Barth9af190c2017-08-08 14:20:43 -0500174 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
Matthew Barth9c726672017-05-18 13:44:46 -0500187 },
188 %endfor
189 }
190 },
191 %endfor
192 }
Matt Spinler78498c92017-04-11 13:59:46 -0500193 },
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500194%endfor
195};
196'''
197
Matt Spinler78498c92017-04-11 13:59:46 -0500198
Matthew Barthbb12c922017-06-13 13:57:40 -0500199def 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
Matthew Barth9af190c2017-08-08 14:20:43 -0500209def 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
Matthew Barth90149802017-08-15 10:51:37 -0500273 # 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
Matthew Barth9af190c2017-08-08 14:20:43 -0500287 return precond
288
289
Gunnar Millsb751f322017-06-06 15:14:11 -0500290def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500291 """
292 Constructs the event entries defined for each zone using the events yaml
293 provided.
294 """
295 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500296
Matthew Barthd4d0f082017-05-16 13:51:10 -0500297 if 'events' in events_data:
298 for e in events_data['events']:
Gunnar Millsb751f322017-06-06 15:14:11 -0500299
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
Matthew Barthd4d0f082017-05-16 13:51:10 -0500312
313 event = {}
Matthew Barth9af190c2017-08-08 14:20:43 -0500314 # Add precondition if given
315 if ('precondition' in e) and \
316 (e['precondition'] is not None):
317 event['pc'] = addPrecondition(e, events_data)
318
Matthew Barthd4d0f082017-05-16 13:51:10 -0500319 # 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 = {}
Matthew Barthbe250832017-08-11 13:16:28 -0500325 members['obj_path'] = groups['type']
Matthew Barth9af190c2017-08-08 14:20:43 -0500326 members['name'] = (groups['type'] +
Matthew Barthd4d0f082017-05-16 13:51:10 -0500327 member)
328 members['interface'] = e['interface']
329 members['property'] = e['property']['name']
330 group.append(members)
331 event['group'] = group
Matthew Barthba102b32017-05-16 16:13:56 -0500332
333 # Add set speed action and function parameters
334 action = {}
335 actions = next(a for a in events_data['actions']
Matthew Barth9c726672017-05-18 13:44:46 -0500336 if a['name'] == e['action']['name'])
Matthew Barthba102b32017-05-16 16:13:56 -0500337 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:
Matthew Barthba102b32017-05-16 16:13:56 -0500351 param['type'] = str(e['action'][p]['type']).lower()
Matthew Barthbb12c922017-06-13 13:57:40 -0500352 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
Matthew Barthba102b32017-05-16 16:13:56 -0500357 params.append(param)
358 action['parameters'] = params
359 event['action'] = action
360
Matthew Barth7e527c12017-05-17 10:14:15 -0500361 # Add property change signal handler
362 signal = []
363 for path in group:
364 signals = {}
Matthew Barthbe250832017-08-11 13:16:28 -0500365 signals['obj_path'] = path['obj_path']
Matthew Barth7e527c12017-05-17 10:14:15 -0500366 signals['path'] = path['name']
367 signals['interface'] = e['interface']
368 signals['property'] = e['property']['name']
369 signals['type'] = e['property']['type']
Matthew Barth7e527c12017-05-17 10:14:15 -0500370 signal.append(signals)
371 event['signal'] = signal
372
Matthew Barth90149802017-08-15 10:51:37 -0500373 # 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
Matthew Barthd4d0f082017-05-16 13:51:10 -0500385 events.append(event)
386
387 return events
388
389
Matt Spinler78498c92017-04-11 13:59:46 -0500390def 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
Gunnar Mills67e95512017-06-02 14:35:18 -0500404 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500405 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
Gunnar Millsee8a2812017-06-02 14:26:47 -0500421def 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):
Matt Spinler78498c92017-04-11 13:59:46 -0500449 """
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 = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500459 # 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:
Gunnar Millsb751f322017-06-06 15:14:11 -0500464 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500465 "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)
Matt Spinler78498c92017-04-11 13:59:46 -0500474
475 zone_group = {}
476 zone_group['conditions'] = conditions
477
478 zones = []
479 for z in group['zones']:
480 zone = {}
481
Gunnar Mills67e95512017-06-02 14:35:18 -0500482 # 'zone' is required
483 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500484 sys.exit("Missing fan zone number in " + zone_yaml)
485
486 zone['num'] = z['zone']
487
488 zone['full_speed'] = z['full_speed']
489
Matthew Barth1de66622017-06-12 13:13:02 -0500490 zone['default_floor'] = z['default_floor']
491
Matthew Bartha9561842017-06-29 11:43:45 -0500492 # '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
Gunnar Mills67e95512017-06-02 14:35:18 -0500500 # 'cooling_profiles' is optional (use 'all' instead)
501 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500502 (z['cooling_profiles'] is None):
503 profiles = ["all"]
504 else:
505 profiles = z['cooling_profiles']
506
507 fans = getFansInZone(z['zone'], profiles, fan_data)
Gunnar Millsb751f322017-06-06 15:14:11 -0500508 events = getEventsInZone(z['zone'], group['zone_conditions'],
509 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500510
511 if len(fans) == 0:
512 sys.exit("Didn't find any fans in zone " + str(zone['num']))
513
514 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500515 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500516 zones.append(zone)
517
518 zone_group['zones'] = zones
519 zone_groups.append(zone_group)
520
521 return zone_groups
522
523
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500524if __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')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500534 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
535 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500536 parser.add_argument('-c', '--zone_conditions_yaml',
537 dest='zone_conditions_yaml',
538 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500539 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
Matthew Barthd4d0f082017-05-16 13:51:10 -0500554 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
Gunnar Millsee8a2812017-06-02 14:26:47 -0500559 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
Matt Spinleree7f6422017-05-09 11:03:14 -0500564 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500565 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500566
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
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500571
572 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
573 with open(output_file, 'w') as output:
Matt Spinleree7f6422017-05-09 11:03:14 -0500574 output.write(Template(tmpl).render(zones=zone_config,
575 mgr_data=manager_config))