blob: 2c0426645855df5e865eeb9365df4c0a4c56461e [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
Matthew Barth702c4a52018-02-28 16:23:11 -060013from mako.lookup import TemplateLookup
Matt Spinlerd08dbe22017-04-11 13:52:54 -050014
Matt Spinler78498c92017-04-11 13:59:46 -050015
Matthew Barth7883f582019-02-14 14:24:46 -060016def parse_cpp_type(typeName):
17 """
18 Take a list of dbus types from YAML and convert it to a recursive cpp
19 formed data structure. Each entry of the original list gets converted
20 into a tuple consisting of the type name and a list with the params
21 for this type,
22 e.g.
23 ['dict', ['string', 'dict', ['string', 'int64']]]
24 is converted to
25 [('dict', [('string', []), ('dict', [('string', []),
26 ('int64', [])]]]
27 """
28
29 if not typeName:
30 return None
31
32 # Type names are _almost_ valid YAML. Insert a , before each [
33 # and then wrap it in a [ ] and it becomes valid YAML (assuming
34 # the user gave us a valid typename).
35 typeArray = yaml.safe_load("[" + ",[".join(typeName.split("[")) + "]")
36 typeTuple = preprocess_yaml_type_array(typeArray).pop(0)
37 return get_cpp_type(typeTuple)
38
39
40def preprocess_yaml_type_array(typeArray):
41 """
42 Flattens an array type into a tuple list that can be used to get the
43 supported cpp type from each element.
44 """
45
46 result = []
47
48 for i in range(len(typeArray)):
49 # Ignore lists because we merge them with the previous element
50 if type(typeArray[i]) is list:
51 continue
52
53 # If there is a next element and it is a list, merge it with the
54 # current element.
55 if i < len(typeArray)-1 and type(typeArray[i+1]) is list:
56 result.append(
57 (typeArray[i],
58 preprocess_yaml_type_array(typeArray[i+1])))
59 else:
60 result.append((typeArray[i], []))
61
62 return result
63
64
65def get_cpp_type(typeTuple):
66 """
67 Take a list of dbus types and perform validity checking, such as:
68 [ variant [ dict [ int32, int32 ], double ] ]
69 This function then converts the type-list into a C++ type string.
70 """
71
72 propertyMap = {
73 'byte': {'cppName': 'uint8_t', 'params': 0},
74 'boolean': {'cppName': 'bool', 'params': 0},
75 'int16': {'cppName': 'int16_t', 'params': 0},
76 'uint16': {'cppName': 'uint16_t', 'params': 0},
77 'int32': {'cppName': 'int32_t', 'params': 0},
78 'uint32': {'cppName': 'uint32_t', 'params': 0},
79 'int64': {'cppName': 'int64_t', 'params': 0},
80 'uint64': {'cppName': 'uint64_t', 'params': 0},
81 'double': {'cppName': 'double', 'params': 0},
82 'string': {'cppName': 'std::string', 'params': 0},
83 'array': {'cppName': 'std::vector', 'params': 1},
84 'dict': {'cppName': 'std::map', 'params': 2}}
85
86 if len(typeTuple) != 2:
87 raise RuntimeError("Invalid typeTuple %s" % typeTuple)
88
89 first = typeTuple[0]
90 entry = propertyMap[first]
91
92 result = entry['cppName']
93
94 # Handle 0-entry parameter lists.
95 if (entry['params'] == 0):
96 if (len(typeTuple[1]) != 0):
97 raise RuntimeError("Invalid typeTuple %s" % typeTuple)
98 else:
99 return result
100
101 # Get the parameter list
102 rest = typeTuple[1]
103
104 # Confirm parameter count matches.
105 if (entry['params'] != -1) and (entry['params'] != len(rest)):
106 raise RuntimeError("Invalid entry count for %s : %s" %
107 (first, rest))
108
109 # Parse each parameter entry, if appropriate, and create C++ template
110 # syntax.
111 result += '<'
112 if entry.get('noparse'):
113 # Do not parse the parameter list, just use the first element
114 # of each tuple and ignore possible parameters
115 result += ", ".join([e[0] for e in rest])
116 else:
117 result += ", ".join(map(lambda e: get_cpp_type(e),
118 rest))
119 result += '>'
120
121 return result
122
123
Matthew Barthbb12c922017-06-13 13:57:40 -0500124def convertToMap(listOfDict):
125 """
126 Converts a list of dictionary entries to a std::map initialization list.
127 """
Matthew Barth9a5b6992018-01-23 15:32:26 -0600128 listOfDict = listOfDict.replace('\'', '\"')
Matthew Barthbb12c922017-06-13 13:57:40 -0500129 listOfDict = listOfDict.replace('[', '{')
130 listOfDict = listOfDict.replace(']', '}')
131 listOfDict = listOfDict.replace(':', ',')
132 return listOfDict
133
134
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600135def genEvent(event):
136 """
137 Generates the source code of an event and returns it as a string
138 """
139 e = "SetSpeedEvent{\n"
140
141 e += "Group{\n"
142 for group in event['groups']:
143 for member in group['members']:
144 e += "{\n"
145 e += "\"" + member['object'] + "\",\n"
146 e += "{\"" + member['interface'] + "\",\n"
147 e += " \"" + member['property'] + "\"}\n"
148 e += "},\n"
149 e += "},\n"
150
151 e += "std::vector<Action>{\n"
152 for a in event['action']:
153 if len(a['parameters']) != 0:
154 e += "make_action(action::" + a['name'] + "(\n"
155 else:
156 e += "make_action(action::" + a['name'] + "\n"
157 for i, p in enumerate(a['parameters']):
158 if (i+1) != len(a['parameters']):
159 e += p + ",\n"
160 else:
161 e += p + ")\n"
162 e += "),\n"
163 e += "},\n"
164
Matthew Barth1b4de262018-03-06 13:03:16 -0600165 e += "std::vector<Trigger>{\n"
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600166 if ('timer' in event['triggers']) and \
167 (event['triggers']['timer'] is not None):
Matthew Barth1b4de262018-03-06 13:03:16 -0600168 e += "\tmake_trigger(trigger::timer(TimerConf{\n"
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600169 e += "\t" + event['triggers']['timer']['interval'] + ",\n"
170 e += "\t" + event['triggers']['timer']['type'] + "\n"
Matthew Barth016bd242018-03-07 16:06:06 -0600171 e += "\t})),\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600172
Matthew Barth016bd242018-03-07 16:06:06 -0600173 if ('signals' in event['triggers']) and \
174 (event['triggers']['signals'] is not None):
175 for s in event['triggers']['signals']:
176 e += "\tmake_trigger(trigger::signal(\n"
177 e += "match::" + s['match'] + "(\n"
178 for i, mp in enumerate(s['mparams']):
179 if (i+1) != len(s['mparams']):
180 e += "\"" + mp + "\",\n"
181 else:
182 e += "\"" + mp + "\"\n"
183 e += "),\n"
184 e += "make_handler(\n"
185 if ('type' in s['sparams']) and (s['sparams']['type'] is not None):
186 e += s['signal'] + "<" + s['sparams']['type'] + ">(\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600187 else:
Matthew Barth016bd242018-03-07 16:06:06 -0600188 e += s['signal'] + "(\n"
189 for sp in s['sparams']['params']:
190 e += s['sparams'][sp] + ",\n"
191 if ('type' in s['hparams']) and (s['hparams']['type'] is not None):
192 e += ("handler::" + s['handler'] +
193 "<" + s['hparams']['type'] + ">(\n")
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600194 else:
Matthew Barth016bd242018-03-07 16:06:06 -0600195 e += "handler::" + s['handler'] + "(\n)"
196 for i, hp in enumerate(s['hparams']['params']):
197 if (i+1) != len(s['hparams']['params']):
198 e += s['hparams'][hp] + ",\n"
199 else:
200 e += s['hparams'][hp] + "\n"
201 e += "))\n"
202 e += ")\n"
203 e += "\t)),\n"
204
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600205 e += "},\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600206
207 e += "}"
208
209 return e
210
211
Matthew Barth6c050692017-12-05 15:30:09 -0600212def getGroups(zNum, zCond, edata, events):
213 """
214 Extract and construct the groups for the given event.
215 """
216 groups = []
217 for eGroups in edata['groups']:
218 if ('zone_conditions' in eGroups) and \
219 (eGroups['zone_conditions'] is not None):
220 # Zone conditions are optional in the events yaml but skip
221 # if this event's condition is not in this zone's conditions
222 if all('name' in z and z['name'] is not None and
223 not any(c['name'] == z['name'] for c in zCond)
224 for z in eGroups['zone_conditions']):
225 continue
226
227 # Zone numbers are optional in the events yaml but skip if this
228 # zone's zone number is not in the event's zone numbers
229 if all('zones' in z and z['zones'] is not None and
230 zNum not in z['zones']
231 for z in eGroups['zone_conditions']):
232 continue
Matthew Barth6c050692017-12-05 15:30:09 -0600233 eGroup = next(g for g in events['groups']
234 if g['name'] == eGroups['name'])
235
236 group = {}
237 members = []
238 group['name'] = eGroup['name']
239 for m in eGroup['members']:
240 member = {}
241 member['path'] = eGroup['type']
242 member['object'] = (eGroup['type'] + m)
243 member['interface'] = eGroups['interface']
244 member['property'] = eGroups['property']['name']
245 member['type'] = eGroups['property']['type']
Matthew Barth18c91032019-01-29 15:36:00 -0600246 # Use defined service to note member on zone object
247 if ('service' in eGroup) and \
248 (eGroup['service'] is not None):
249 member['service'] = eGroup['service']
Matthew Barth6c050692017-12-05 15:30:09 -0600250 # Add expected group member's property value if given
251 if ('value' in eGroups['property']) and \
252 (eGroups['property']['value'] is not None):
253 if isinstance(eGroups['property']['value'], str) or \
254 "string" in str(member['type']).lower():
255 member['value'] = (
256 "\"" + eGroups['property']['value'] + "\"")
257 else:
258 member['value'] = eGroups['property']['value']
259 members.append(member)
260 group['members'] = members
261 groups.append(group)
262 return groups
263
264
Matthew Bartha69465a2018-03-02 13:50:59 -0600265def getSignal(eGrps, eTrig, events):
266 """
267 Extracts and constructs for each group member a signal
268 subscription of each match listed in the trigger.
269 """
270 signals = []
271 for group in eGrps:
272 for member in group['members']:
273 for eMatches in eTrig['matches']:
274 signal = {}
275 eMatch = next(m for m in events['matches']
276 if m['name'] == eMatches['name'])
277 # If service not given, subscribe to signal match
278 if ('service' not in member):
279 signal['match'] = eMatch['name']
280 params = []
281 if ('parameters' in eMatch) and \
282 (eMatch['parameters'] is not None):
283 for p in eMatch['parameters']:
284 params.append(member[str(p)])
285 signal['mparams'] = params
286
287 if ('parameters' in eMatch['signal']) and \
288 (eMatch['signal']['parameters'] is not None):
289 eSignal = eMatch['signal']
290 else:
291 eSignal = next(s for s in events['signals']
292 if s['name'] == eMatch['signal'])
293 signal['signal'] = eSignal['name']
294 sparams = {}
295 if ('parameters' in eSignal) and \
296 (eSignal['parameters'] is not None):
297 splist = []
298 for p in eSignal['parameters']:
299 sp = str(p)
300 if (sp != 'type'):
301 splist.append(sp)
302 if (sp != 'group'):
303 sparams[sp] = "\"" + member[sp] + "\""
304 else:
305 sparams[sp] = "Group{\n"
306 for m in group['members']:
307 sparams[sp] += (
308 "{\n" +
309 "\"" + str(m['object']) + "\",\n" +
310 "{\"" + str(m['interface']) + "\"," +
311 "\"" + str(m['property']) + "\"}\n" +
312 "},\n")
313 sparams[sp] += "}"
314 else:
315 sparams[sp] = member[sp]
316 sparams['params'] = splist
317 signal['sparams'] = sparams
318 # Add signal handler
319 eHandler = next(h for h in events['handlers']
320 if h['name'] == eSignal['handler'])
321 signal['handler'] = eHandler['name']
322 hparams = {}
323 if ('parameters' in eHandler) and \
324 (eHandler['parameters'] is not None):
325 hplist = []
326 for p in eHandler['parameters']:
327 hp = str(p)
328 if (hp != 'type'):
329 hplist.append(hp)
330 if (hp != 'group'):
331 hparams[hp] = "\"" + member[hp] + "\""
332 else:
333 hparams[hp] = "Group{\n"
334 for m in group['members']:
335 hparams[hp] += (
336 "{\n" +
337 "\"" + str(m['object']) + "\",\n" +
338 "{\"" + str(m['interface']) + "\"," +
339 "\"" + str(m['property']) + "\"}\n" +
340 "},\n")
341 hparams[hp] += "}"
342 else:
343 hparams[hp] = member[hp]
344 hparams['params'] = hplist
345 signal['hparams'] = hparams
346 signals.append(signal)
347 return signals
348
349
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600350def getTimer(eTrig):
351 """
352 Extracts and constructs the required parameters for an
353 event timer.
354 """
355 timer = {}
356 timer['interval'] = (
357 "static_cast<std::chrono::microseconds>" +
358 "(" + str(eTrig['interval']) + ")")
359 timer['type'] = "TimerType::" + str(eTrig['type'])
360 return timer
361
362
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600363def getActions(zNum, zCond, edata, actions, events):
Matthew Barth9df74752017-10-11 14:39:31 -0500364 """
365 Extracts and constructs the make_action function call for
366 all the actions within the given event.
367 """
368 action = []
369 for eActions in actions['actions']:
370 actions = {}
371 eAction = next(a for a in events['actions']
372 if a['name'] == eActions['name'])
373 actions['name'] = eAction['name']
374 params = []
375 if ('parameters' in eAction) and \
376 (eAction['parameters'] is not None):
377 for p in eAction['parameters']:
378 param = "static_cast<"
379 if type(eActions[p]) is not dict:
380 if p == 'actions':
381 param = "std::vector<Action>{"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600382 pActs = getActions(zNum,
383 zCond,
384 edata,
385 eActions,
386 events)
Matthew Barth9df74752017-10-11 14:39:31 -0500387 for a in pActs:
388 if (len(a['parameters']) != 0):
389 param += (
390 "make_action(action::" +
391 a['name'] +
392 "(\n")
393 for i, ap in enumerate(a['parameters']):
394 if (i+1) != len(a['parameters']):
395 param += (ap + ",")
396 else:
397 param += (ap + ")")
398 else:
399 param += ("make_action(action::" + a['name'])
400 param += "),"
401 param += "}"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600402 elif p == 'defevents' or p == 'altevents':
403 param = "std::vector<SetSpeedEvent>{\n"
404 for i, e in enumerate(eActions[p]):
405 aEvent = getEvent(zNum, zCond, e, events)
406 if not aEvent:
407 continue
408 if (i+1) != len(eActions[p]):
409 param += genEvent(aEvent) + ",\n"
410 else:
411 param += genEvent(aEvent) + "\n"
412 param += "\t}"
Matthew Barth9df74752017-10-11 14:39:31 -0500413 elif p == 'property':
Matthew Barth9a5b6992018-01-23 15:32:26 -0600414 if isinstance(eActions[p], str) or \
Matthew Barth6c050692017-12-05 15:30:09 -0600415 "string" in str(eActions[p]['type']).lower():
Matthew Barth9a5b6992018-01-23 15:32:26 -0600416 param += (
Matthew Barth6c050692017-12-05 15:30:09 -0600417 str(eActions[p]['type']).lower() +
Matthew Barth9a5b6992018-01-23 15:32:26 -0600418 ">(\"" + str(eActions[p]) + "\")")
419 else:
420 param += (
Matthew Barth6c050692017-12-05 15:30:09 -0600421 str(eActions[p]['type']).lower() +
422 ">(" + str(eActions[p]['value']).lower() + ")")
Matthew Barth9df74752017-10-11 14:39:31 -0500423 else:
424 # Default type to 'size_t' when not given
425 param += ("size_t>(" + str(eActions[p]).lower() + ")")
426 else:
427 if p == 'timer':
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600428 t = getTimer(eActions[p])
Matthew Barth9df74752017-10-11 14:39:31 -0500429 param = (
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600430 "TimerConf{" + t['interval'] + "," +
431 t['type'] + "}")
Matthew Barth9df74752017-10-11 14:39:31 -0500432 else:
433 param += (str(eActions[p]['type']).lower() + ">(")
434 if p != 'map':
Matthew Barth9a5b6992018-01-23 15:32:26 -0600435 if isinstance(eActions[p]['value'], str) or \
436 "string" in str(eActions[p]['type']).lower():
437 param += \
438 "\"" + str(eActions[p]['value']) + "\")"
439 else:
440 param += \
441 str(eActions[p]['value']).lower() + ")"
Matthew Barth9df74752017-10-11 14:39:31 -0500442 else:
443 param += (
444 str(eActions[p]['type']).lower() +
445 convertToMap(str(eActions[p]['value'])) + ")")
446 params.append(param)
447 actions['parameters'] = params
448 action.append(actions)
449 return action
450
451
Matthew Barth7f272fd2017-09-12 16:16:56 -0500452def getEvent(zone_num, zone_conditions, e, events_data):
453 """
454 Parses the sections of an event and populates the properties
455 that construct an event within the generated source.
456 """
457 event = {}
Matthew Barth7f272fd2017-09-12 16:16:56 -0500458
Matthew Barth6c050692017-12-05 15:30:09 -0600459 # Add set speed event groups
460 grps = getGroups(zone_num, zone_conditions, e, events_data)
461 if not grps:
Matthew Barth7f272fd2017-09-12 16:16:56 -0500462 return
Matthew Barth6c050692017-12-05 15:30:09 -0600463 event['groups'] = grps
Matthew Barth7f272fd2017-09-12 16:16:56 -0500464
Matthew Barthe3d1c4a2018-01-11 13:53:49 -0600465 # Add optional set speed actions and function parameters
466 event['action'] = []
467 if ('actions' in e) and \
468 (e['actions'] is not None):
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600469 event['action'] = getActions(zone_num,
470 zone_conditions,
471 e,
472 e,
473 events_data)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500474
Matthew Bartha69465a2018-03-02 13:50:59 -0600475 # Add event triggers
476 event['triggers'] = {}
477 for trig in e['triggers']:
478 triggers = []
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600479 if (trig['name'] == "timer"):
480 event['triggers']['timer'] = getTimer(trig)
481 elif (trig['name'] == "signal"):
Matthew Bartha69465a2018-03-02 13:50:59 -0600482 if ('signals' not in event['triggers']):
483 event['triggers']['signals'] = []
484 triggers = getSignal(event['groups'], trig, events_data)
485 event['triggers']['signals'].extend(triggers)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500486
Matthew Barth7f272fd2017-09-12 16:16:56 -0500487 return event
488
489
490def addPrecondition(zNum, zCond, event, events_data):
Matthew Barth9af190c2017-08-08 14:20:43 -0500491 """
492 Parses the precondition section of an event and populates the necessary
493 structures to generate a precondition for a set speed event.
494 """
495 precond = {}
496 # Add set speed event precondition group
Matthew Barth6c050692017-12-05 15:30:09 -0600497 grps = getGroups(zNum, zCond, event['precondition'], events_data)
498 if not grps:
499 return
500 precond['pcgrps'] = grps
Matthew Barth9af190c2017-08-08 14:20:43 -0500501
Matthew Barth7f272fd2017-09-12 16:16:56 -0500502 # Add set speed event precondition actions
503 pc = []
504 pcs = {}
505 pcs['name'] = event['precondition']['name']
506 epc = next(p for p in events_data['preconditions']
Matthew Barth9af190c2017-08-08 14:20:43 -0500507 if p['name'] == event['precondition']['name'])
508 params = []
Matthew Barth7f272fd2017-09-12 16:16:56 -0500509 for p in epc['parameters']:
Matthew Barth9af190c2017-08-08 14:20:43 -0500510 param = {}
511 if p == 'groups':
512 param['type'] = "std::vector<PrecondGroup>"
513 param['open'] = "{"
514 param['close'] = "}"
515 values = []
Matthew Barth6c050692017-12-05 15:30:09 -0600516 for group in precond['pcgrps']:
517 for pcgrp in group['members']:
518 value = {}
519 value['value'] = (
520 "PrecondGroup{\"" +
521 str(pcgrp['object']) + "\",\"" +
522 str(pcgrp['interface']) + "\",\"" +
523 str(pcgrp['property']) + "\"," +
524 "static_cast<" +
525 str(pcgrp['type']).lower() + ">")
526 if isinstance(pcgrp['value'], str) or \
527 "string" in str(pcgrp['type']).lower():
528 value['value'] += ("(" + str(pcgrp['value']) + ")}")
529 else:
530 value['value'] += \
531 ("(" + str(pcgrp['value']).lower() + ")}")
532 values.append(value)
Matthew Barth9af190c2017-08-08 14:20:43 -0500533 param['values'] = values
534 params.append(param)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500535 pcs['params'] = params
536 pc.append(pcs)
Matthew Barth9af190c2017-08-08 14:20:43 -0500537 precond['pcact'] = pc
538
Matthew Barth7f272fd2017-09-12 16:16:56 -0500539 pcevents = []
540 for pce in event['precondition']['events']:
541 pcevent = getEvent(zNum, zCond, pce, events_data)
542 if not pcevent:
543 continue
544 pcevents.append(pcevent)
545 precond['pcevts'] = pcevents
546
Matthew Barthf20c3212018-03-02 14:42:55 -0600547 # Add precondition event triggers
548 precond['triggers'] = {}
549 for trig in event['precondition']['triggers']:
550 triggers = []
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600551 if (trig['name'] == "timer"):
552 precond['triggers']['pctime'] = getTimer(trig)
553 elif (trig['name'] == "signal"):
Matthew Barthf20c3212018-03-02 14:42:55 -0600554 if ('pcsigs' not in precond['triggers']):
555 precond['triggers']['pcsigs'] = []
556 triggers = getSignal(precond['pcgrps'], trig, events_data)
557 precond['triggers']['pcsigs'].extend(triggers)
Matthew Barth9af190c2017-08-08 14:20:43 -0500558
559 return precond
560
561
Gunnar Millsb751f322017-06-06 15:14:11 -0500562def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500563 """
564 Constructs the event entries defined for each zone using the events yaml
565 provided.
566 """
567 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500568
Matthew Barthd4d0f082017-05-16 13:51:10 -0500569 if 'events' in events_data:
570 for e in events_data['events']:
Matthew Barthd4d0f082017-05-16 13:51:10 -0500571 event = {}
Matthew Barth9af190c2017-08-08 14:20:43 -0500572 # Add precondition if given
573 if ('precondition' in e) and \
574 (e['precondition'] is not None):
Matthew Barth7f272fd2017-09-12 16:16:56 -0500575 event['pc'] = addPrecondition(zone_num,
576 zone_conditions,
577 e,
578 events_data)
Matthew Barth90149802017-08-15 10:51:37 -0500579 else:
Matthew Barth7f272fd2017-09-12 16:16:56 -0500580 event = getEvent(zone_num, zone_conditions, e, events_data)
581 if not event:
582 continue
Matthew Barthd4d0f082017-05-16 13:51:10 -0500583 events.append(event)
584
585 return events
586
587
Matt Spinler78498c92017-04-11 13:59:46 -0500588def getFansInZone(zone_num, profiles, fan_data):
589 """
590 Parses the fan definition YAML files to find the fans
591 that match both the zone passed in and one of the
592 cooling profiles.
593 """
594
595 fans = []
596
597 for f in fan_data['fans']:
598
599 if zone_num != f['cooling_zone']:
600 continue
601
Gunnar Mills67e95512017-06-02 14:35:18 -0500602 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500603 if f.get('cooling_profile') is None:
604 profile = "all"
605 else:
606 profile = f['cooling_profile']
607
608 if profile not in profiles:
609 continue
610
611 fan = {}
612 fan['name'] = f['inventory']
613 fan['sensors'] = f['sensors']
Lei YU069e4402018-01-31 16:47:37 +0800614 fan['target_interface'] = f.get(
615 'target_interface',
616 'xyz.openbmc_project.Control.FanSpeed')
Matt Spinler78498c92017-04-11 13:59:46 -0500617 fans.append(fan)
618
619 return fans
620
621
Matthew Barth7883f582019-02-14 14:24:46 -0600622def getIfacesInZone(zone_ifaces):
623 """
624 Parse given interfaces for a zone for associating a zone with an interface
625 and set any properties listed to defined values upon fan control starting
626 on the zone.
627 """
628
629 ifaces = []
630 for i in zone_ifaces:
631 iface = {}
632 # Interface name not needed yet for fan zones but
633 # may be necessary as more interfaces are extended by the zones
634 iface['name'] = i['name']
635
636 if ('properties' in i) and \
637 (i['properties'] is not None):
638 props = []
639 for p in i['properties']:
640 prop = {}
Matthew Barth59096e52019-02-18 12:23:38 -0600641 prop['name'] = p['name']
642 prop['func'] = str(p['name']).lower()
Matthew Barth7883f582019-02-14 14:24:46 -0600643 prop['type'] = parse_cpp_type(p['type'])
Matthew Barth59096e52019-02-18 12:23:38 -0600644 if ('persist' in p):
645 persist = p['persist']
646 if (persist is not None):
647 if (isinstance(persist, bool)):
648 prop['persist'] = 'true' if persist else 'false'
649 else:
650 prop['persist'] = 'false'
Matthew Barth7883f582019-02-14 14:24:46 -0600651 vals = []
652 for v in p['values']:
653 val = v['value']
654 if (val is not None):
655 if (isinstance(val, bool)):
656 # Convert True/False to 'true'/'false'
657 val = 'true' if val else 'false'
658 elif (isinstance(val, str)):
659 # Wrap strings with double-quotes
660 val = "\"" + val + "\""
661 vals.append(val)
662 prop['values'] = vals
663 props.append(prop)
664 iface['props'] = props
665 ifaces.append(iface)
666
667 return ifaces
668
669
Gunnar Millsee8a2812017-06-02 14:26:47 -0500670def getConditionInZoneConditions(zone_condition, zone_conditions_data):
671 """
672 Parses the zone conditions definition YAML files to find the condition
673 that match both the zone condition passed in.
674 """
675
676 condition = {}
677
678 for c in zone_conditions_data['conditions']:
679
680 if zone_condition != c['name']:
681 continue
682 condition['type'] = c['type']
683 properties = []
684 for p in c['properties']:
685 property = {}
686 property['property'] = p['property']
687 property['interface'] = p['interface']
688 property['path'] = p['path']
689 property['type'] = p['type'].lower()
690 property['value'] = str(p['value']).lower()
691 properties.append(property)
692 condition['properties'] = properties
693
694 return condition
695
696
697def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
Matt Spinler78498c92017-04-11 13:59:46 -0500698 """
699 Combines the zone definition YAML and fan
700 definition YAML to create a data structure defining
701 the fan cooling zones.
702 """
703
704 zone_groups = []
705
706 for group in zone_data:
707 conditions = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500708 # zone conditions are optional
709 if 'zone_conditions' in group and group['zone_conditions'] is not None:
710 for c in group['zone_conditions']:
711
712 if not zone_conditions_data:
Gunnar Millsb751f322017-06-06 15:14:11 -0500713 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500714 "zone_conditions used in zone YAML")
715
716 condition = getConditionInZoneConditions(c['name'],
717 zone_conditions_data)
718
719 if not condition:
720 sys.exit("Missing zone condition " + c['name'])
721
722 conditions.append(condition)
Matt Spinler78498c92017-04-11 13:59:46 -0500723
724 zone_group = {}
725 zone_group['conditions'] = conditions
726
727 zones = []
728 for z in group['zones']:
729 zone = {}
730
Gunnar Mills67e95512017-06-02 14:35:18 -0500731 # 'zone' is required
732 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500733 sys.exit("Missing fan zone number in " + zone_yaml)
734
735 zone['num'] = z['zone']
736
737 zone['full_speed'] = z['full_speed']
738
Matthew Barth1de66622017-06-12 13:13:02 -0500739 zone['default_floor'] = z['default_floor']
740
Matthew Bartha9561842017-06-29 11:43:45 -0500741 # 'increase_delay' is optional (use 0 by default)
742 key = 'increase_delay'
743 zone[key] = z.setdefault(key, 0)
744
745 # 'decrease_interval' is optional (use 0 by default)
746 key = 'decrease_interval'
747 zone[key] = z.setdefault(key, 0)
748
Gunnar Mills67e95512017-06-02 14:35:18 -0500749 # 'cooling_profiles' is optional (use 'all' instead)
750 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500751 (z['cooling_profiles'] is None):
752 profiles = ["all"]
753 else:
754 profiles = z['cooling_profiles']
755
Matthew Barth7883f582019-02-14 14:24:46 -0600756 # 'interfaces' is optional (no default)
Matthew Barth64099cd2019-02-18 09:43:12 -0600757 ifaces = []
Matthew Barth7883f582019-02-14 14:24:46 -0600758 if ('interfaces' in z) and \
759 (z['interfaces'] is not None):
760 ifaces = getIfacesInZone(z['interfaces'])
761
Matt Spinler78498c92017-04-11 13:59:46 -0500762 fans = getFansInZone(z['zone'], profiles, fan_data)
Gunnar Millsb751f322017-06-06 15:14:11 -0500763 events = getEventsInZone(z['zone'], group['zone_conditions'],
764 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500765
766 if len(fans) == 0:
767 sys.exit("Didn't find any fans in zone " + str(zone['num']))
768
Matthew Barth7883f582019-02-14 14:24:46 -0600769 if (ifaces):
770 zone['ifaces'] = ifaces
Matt Spinler78498c92017-04-11 13:59:46 -0500771 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500772 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500773 zones.append(zone)
774
775 zone_group['zones'] = zones
776 zone_groups.append(zone_group)
777
778 return zone_groups
779
780
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500781if __name__ == '__main__':
782 parser = ArgumentParser(
783 description="Phosphor fan zone definition parser")
784
785 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
786 default="example/zones.yaml",
787 help='fan zone definitional yaml')
788 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
789 default="example/fans.yaml",
790 help='fan definitional yaml')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500791 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
792 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500793 parser.add_argument('-c', '--zone_conditions_yaml',
794 dest='zone_conditions_yaml',
795 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500796 parser.add_argument('-o', '--output_dir', dest='output_dir',
797 default=".",
798 help='output directory')
799 args = parser.parse_args()
800
801 if not args.zone_yaml or not args.fan_yaml:
802 parser.print_usage()
William A. Kennington III3e781062018-10-19 17:18:34 -0700803 sys.exit(1)
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500804
805 with open(args.zone_yaml, 'r') as zone_input:
806 zone_data = yaml.safe_load(zone_input) or {}
807
808 with open(args.fan_yaml, 'r') as fan_input:
809 fan_data = yaml.safe_load(fan_input) or {}
810
Matthew Barthd4d0f082017-05-16 13:51:10 -0500811 events_data = {}
812 if args.events_yaml:
813 with open(args.events_yaml, 'r') as events_input:
814 events_data = yaml.safe_load(events_input) or {}
815
Gunnar Millsee8a2812017-06-02 14:26:47 -0500816 zone_conditions_data = {}
817 if args.zone_conditions_yaml:
818 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
819 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
820
Matt Spinleree7f6422017-05-09 11:03:14 -0500821 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500822 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500823
824 manager_config = zone_data.get('manager_configuration', {})
825
826 if manager_config.get('power_on_delay') is None:
827 manager_config['power_on_delay'] = 0
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500828
Matthew Barth702c4a52018-02-28 16:23:11 -0600829 tmpls_dir = os.path.join(
830 os.path.dirname(os.path.realpath(__file__)),
831 "templates")
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500832 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
Matthew Barth702c4a52018-02-28 16:23:11 -0600833 if sys.version_info < (3, 0):
834 lkup = TemplateLookup(
835 directories=tmpls_dir.split(),
836 disable_unicode=True)
837 else:
838 lkup = TemplateLookup(
839 directories=tmpls_dir.split())
840 tmpl = lkup.get_template('fan_zone_defs.mako.cpp')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500841 with open(output_file, 'w') as output:
Matthew Barth702c4a52018-02-28 16:23:11 -0600842 output.write(tmpl.render(zones=zone_config,
843 mgr_data=manager_config))