blob: 5f1de4565fff8f283ac50abcca5ab46234affd21 [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
165 e += "TimerConf{\n"
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600166 if ('timer' in event['triggers']) and \
167 (event['triggers']['timer'] is not None):
168 e += "\t" + event['triggers']['timer']['interval'] + ",\n"
169 e += "\t" + event['triggers']['timer']['type'] + "\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600170 e += "},\n"
171
172 e += "std::vector<Signal>{\n"
Matthew Bartha69465a2018-03-02 13:50:59 -0600173 for s in event['triggers']['signals']:
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600174 e += "Signal{\n"
175 e += "match::" + s['match'] + "(\n"
176 for i, mp in enumerate(s['mparams']):
177 if (i+1) != len(s['mparams']):
178 e += "\"" + mp + "\",\n"
179 else:
180 e += "\"" + mp + "\"\n"
181 e += "),\n"
182 e += "make_handler(\n"
183 if ('type' in s['sparams']) and (s['sparams']['type'] is not None):
184 e += s['signal'] + "<" + s['sparams']['type'] + ">(\n"
185 else:
186 e += s['signal'] + "(\n"
187 for sp in s['sparams']['params']:
188 e += s['sparams'][sp] + ",\n"
189 if ('type' in s['hparams']) and (s['hparams']['type'] is not None):
190 e += ("handler::" + s['handler'] +
191 "<" + s['hparams']['type'] + ">(\n")
192 else:
193 e += "handler::" + s['handler'] + "(\n)"
194 for i, hp in enumerate(s['hparams']['params']):
195 if (i+1) != len(s['hparams']['params']):
196 e += s['hparams'][hp] + ",\n"
197 else:
198 e += s['hparams'][hp] + "\n"
199 e += "))\n"
200 e += ")\n"
201 e += "},\n"
202 e += "}\n"
203
204 e += "}"
205
206 return e
207
208
Matthew Barth6c050692017-12-05 15:30:09 -0600209def getGroups(zNum, zCond, edata, events):
210 """
211 Extract and construct the groups for the given event.
212 """
213 groups = []
214 for eGroups in edata['groups']:
215 if ('zone_conditions' in eGroups) and \
216 (eGroups['zone_conditions'] is not None):
217 # Zone conditions are optional in the events yaml but skip
218 # if this event's condition is not in this zone's conditions
219 if all('name' in z and z['name'] is not None and
220 not any(c['name'] == z['name'] for c in zCond)
221 for z in eGroups['zone_conditions']):
222 continue
223
224 # Zone numbers are optional in the events yaml but skip if this
225 # zone's zone number is not in the event's zone numbers
226 if all('zones' in z and z['zones'] is not None and
227 zNum not in z['zones']
228 for z in eGroups['zone_conditions']):
229 continue
Matthew Barth6c050692017-12-05 15:30:09 -0600230 eGroup = next(g for g in events['groups']
231 if g['name'] == eGroups['name'])
232
233 group = {}
234 members = []
235 group['name'] = eGroup['name']
236 for m in eGroup['members']:
237 member = {}
238 member['path'] = eGroup['type']
239 member['object'] = (eGroup['type'] + m)
240 member['interface'] = eGroups['interface']
241 member['property'] = eGroups['property']['name']
242 member['type'] = eGroups['property']['type']
Matthew Barth18c91032019-01-29 15:36:00 -0600243 # Use defined service to note member on zone object
244 if ('service' in eGroup) and \
245 (eGroup['service'] is not None):
246 member['service'] = eGroup['service']
Matthew Barth6c050692017-12-05 15:30:09 -0600247 # Add expected group member's property value if given
248 if ('value' in eGroups['property']) and \
249 (eGroups['property']['value'] is not None):
250 if isinstance(eGroups['property']['value'], str) or \
251 "string" in str(member['type']).lower():
252 member['value'] = (
253 "\"" + eGroups['property']['value'] + "\"")
254 else:
255 member['value'] = eGroups['property']['value']
256 members.append(member)
257 group['members'] = members
258 groups.append(group)
259 return groups
260
261
Matthew Bartha69465a2018-03-02 13:50:59 -0600262def getSignal(eGrps, eTrig, events):
263 """
264 Extracts and constructs for each group member a signal
265 subscription of each match listed in the trigger.
266 """
267 signals = []
268 for group in eGrps:
269 for member in group['members']:
270 for eMatches in eTrig['matches']:
271 signal = {}
272 eMatch = next(m for m in events['matches']
273 if m['name'] == eMatches['name'])
274 # If service not given, subscribe to signal match
275 if ('service' not in member):
276 signal['match'] = eMatch['name']
277 params = []
278 if ('parameters' in eMatch) and \
279 (eMatch['parameters'] is not None):
280 for p in eMatch['parameters']:
281 params.append(member[str(p)])
282 signal['mparams'] = params
283
284 if ('parameters' in eMatch['signal']) and \
285 (eMatch['signal']['parameters'] is not None):
286 eSignal = eMatch['signal']
287 else:
288 eSignal = next(s for s in events['signals']
289 if s['name'] == eMatch['signal'])
290 signal['signal'] = eSignal['name']
291 sparams = {}
292 if ('parameters' in eSignal) and \
293 (eSignal['parameters'] is not None):
294 splist = []
295 for p in eSignal['parameters']:
296 sp = str(p)
297 if (sp != 'type'):
298 splist.append(sp)
299 if (sp != 'group'):
300 sparams[sp] = "\"" + member[sp] + "\""
301 else:
302 sparams[sp] = "Group{\n"
303 for m in group['members']:
304 sparams[sp] += (
305 "{\n" +
306 "\"" + str(m['object']) + "\",\n" +
307 "{\"" + str(m['interface']) + "\"," +
308 "\"" + str(m['property']) + "\"}\n" +
309 "},\n")
310 sparams[sp] += "}"
311 else:
312 sparams[sp] = member[sp]
313 sparams['params'] = splist
314 signal['sparams'] = sparams
315 # Add signal handler
316 eHandler = next(h for h in events['handlers']
317 if h['name'] == eSignal['handler'])
318 signal['handler'] = eHandler['name']
319 hparams = {}
320 if ('parameters' in eHandler) and \
321 (eHandler['parameters'] is not None):
322 hplist = []
323 for p in eHandler['parameters']:
324 hp = str(p)
325 if (hp != 'type'):
326 hplist.append(hp)
327 if (hp != 'group'):
328 hparams[hp] = "\"" + member[hp] + "\""
329 else:
330 hparams[hp] = "Group{\n"
331 for m in group['members']:
332 hparams[hp] += (
333 "{\n" +
334 "\"" + str(m['object']) + "\",\n" +
335 "{\"" + str(m['interface']) + "\"," +
336 "\"" + str(m['property']) + "\"}\n" +
337 "},\n")
338 hparams[hp] += "}"
339 else:
340 hparams[hp] = member[hp]
341 hparams['params'] = hplist
342 signal['hparams'] = hparams
343 signals.append(signal)
344 return signals
345
346
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600347def getTimer(eTrig):
348 """
349 Extracts and constructs the required parameters for an
350 event timer.
351 """
352 timer = {}
353 timer['interval'] = (
354 "static_cast<std::chrono::microseconds>" +
355 "(" + str(eTrig['interval']) + ")")
356 timer['type'] = "TimerType::" + str(eTrig['type'])
357 return timer
358
359
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600360def getActions(zNum, zCond, edata, actions, events):
Matthew Barth9df74752017-10-11 14:39:31 -0500361 """
362 Extracts and constructs the make_action function call for
363 all the actions within the given event.
364 """
365 action = []
366 for eActions in actions['actions']:
367 actions = {}
368 eAction = next(a for a in events['actions']
369 if a['name'] == eActions['name'])
370 actions['name'] = eAction['name']
371 params = []
372 if ('parameters' in eAction) and \
373 (eAction['parameters'] is not None):
374 for p in eAction['parameters']:
375 param = "static_cast<"
376 if type(eActions[p]) is not dict:
377 if p == 'actions':
378 param = "std::vector<Action>{"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600379 pActs = getActions(zNum,
380 zCond,
381 edata,
382 eActions,
383 events)
Matthew Barth9df74752017-10-11 14:39:31 -0500384 for a in pActs:
385 if (len(a['parameters']) != 0):
386 param += (
387 "make_action(action::" +
388 a['name'] +
389 "(\n")
390 for i, ap in enumerate(a['parameters']):
391 if (i+1) != len(a['parameters']):
392 param += (ap + ",")
393 else:
394 param += (ap + ")")
395 else:
396 param += ("make_action(action::" + a['name'])
397 param += "),"
398 param += "}"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600399 elif p == 'defevents' or p == 'altevents':
400 param = "std::vector<SetSpeedEvent>{\n"
401 for i, e in enumerate(eActions[p]):
402 aEvent = getEvent(zNum, zCond, e, events)
403 if not aEvent:
404 continue
405 if (i+1) != len(eActions[p]):
406 param += genEvent(aEvent) + ",\n"
407 else:
408 param += genEvent(aEvent) + "\n"
409 param += "\t}"
Matthew Barth9df74752017-10-11 14:39:31 -0500410 elif p == 'property':
Matthew Barth9a5b6992018-01-23 15:32:26 -0600411 if isinstance(eActions[p], str) or \
Matthew Barth6c050692017-12-05 15:30:09 -0600412 "string" in str(eActions[p]['type']).lower():
Matthew Barth9a5b6992018-01-23 15:32:26 -0600413 param += (
Matthew Barth6c050692017-12-05 15:30:09 -0600414 str(eActions[p]['type']).lower() +
Matthew Barth9a5b6992018-01-23 15:32:26 -0600415 ">(\"" + str(eActions[p]) + "\")")
416 else:
417 param += (
Matthew Barth6c050692017-12-05 15:30:09 -0600418 str(eActions[p]['type']).lower() +
419 ">(" + str(eActions[p]['value']).lower() + ")")
Matthew Barth9df74752017-10-11 14:39:31 -0500420 else:
421 # Default type to 'size_t' when not given
422 param += ("size_t>(" + str(eActions[p]).lower() + ")")
423 else:
424 if p == 'timer':
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600425 t = getTimer(eActions[p])
Matthew Barth9df74752017-10-11 14:39:31 -0500426 param = (
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600427 "TimerConf{" + t['interval'] + "," +
428 t['type'] + "}")
Matthew Barth9df74752017-10-11 14:39:31 -0500429 else:
430 param += (str(eActions[p]['type']).lower() + ">(")
431 if p != 'map':
Matthew Barth9a5b6992018-01-23 15:32:26 -0600432 if isinstance(eActions[p]['value'], str) or \
433 "string" in str(eActions[p]['type']).lower():
434 param += \
435 "\"" + str(eActions[p]['value']) + "\")"
436 else:
437 param += \
438 str(eActions[p]['value']).lower() + ")"
Matthew Barth9df74752017-10-11 14:39:31 -0500439 else:
440 param += (
441 str(eActions[p]['type']).lower() +
442 convertToMap(str(eActions[p]['value'])) + ")")
443 params.append(param)
444 actions['parameters'] = params
445 action.append(actions)
446 return action
447
448
Matthew Barth7f272fd2017-09-12 16:16:56 -0500449def getEvent(zone_num, zone_conditions, e, events_data):
450 """
451 Parses the sections of an event and populates the properties
452 that construct an event within the generated source.
453 """
454 event = {}
Matthew Barth7f272fd2017-09-12 16:16:56 -0500455
Matthew Barth6c050692017-12-05 15:30:09 -0600456 # Add set speed event groups
457 grps = getGroups(zone_num, zone_conditions, e, events_data)
458 if not grps:
Matthew Barth7f272fd2017-09-12 16:16:56 -0500459 return
Matthew Barth6c050692017-12-05 15:30:09 -0600460 event['groups'] = grps
Matthew Barth7f272fd2017-09-12 16:16:56 -0500461
Matthew Barthe3d1c4a2018-01-11 13:53:49 -0600462 # Add optional set speed actions and function parameters
463 event['action'] = []
464 if ('actions' in e) and \
465 (e['actions'] is not None):
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600466 event['action'] = getActions(zone_num,
467 zone_conditions,
468 e,
469 e,
470 events_data)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500471
Matthew Bartha69465a2018-03-02 13:50:59 -0600472 # Add event triggers
473 event['triggers'] = {}
474 for trig in e['triggers']:
475 triggers = []
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600476 if (trig['name'] == "timer"):
477 event['triggers']['timer'] = getTimer(trig)
478 elif (trig['name'] == "signal"):
Matthew Bartha69465a2018-03-02 13:50:59 -0600479 if ('signals' not in event['triggers']):
480 event['triggers']['signals'] = []
481 triggers = getSignal(event['groups'], trig, events_data)
482 event['triggers']['signals'].extend(triggers)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500483
Matthew Barth7f272fd2017-09-12 16:16:56 -0500484 return event
485
486
487def addPrecondition(zNum, zCond, event, events_data):
Matthew Barth9af190c2017-08-08 14:20:43 -0500488 """
489 Parses the precondition section of an event and populates the necessary
490 structures to generate a precondition for a set speed event.
491 """
492 precond = {}
493 # Add set speed event precondition group
Matthew Barth6c050692017-12-05 15:30:09 -0600494 grps = getGroups(zNum, zCond, event['precondition'], events_data)
495 if not grps:
496 return
497 precond['pcgrps'] = grps
Matthew Barth9af190c2017-08-08 14:20:43 -0500498
Matthew Barth7f272fd2017-09-12 16:16:56 -0500499 # Add set speed event precondition actions
500 pc = []
501 pcs = {}
502 pcs['name'] = event['precondition']['name']
503 epc = next(p for p in events_data['preconditions']
Matthew Barth9af190c2017-08-08 14:20:43 -0500504 if p['name'] == event['precondition']['name'])
505 params = []
Matthew Barth7f272fd2017-09-12 16:16:56 -0500506 for p in epc['parameters']:
Matthew Barth9af190c2017-08-08 14:20:43 -0500507 param = {}
508 if p == 'groups':
509 param['type'] = "std::vector<PrecondGroup>"
510 param['open'] = "{"
511 param['close'] = "}"
512 values = []
Matthew Barth6c050692017-12-05 15:30:09 -0600513 for group in precond['pcgrps']:
514 for pcgrp in group['members']:
515 value = {}
516 value['value'] = (
517 "PrecondGroup{\"" +
518 str(pcgrp['object']) + "\",\"" +
519 str(pcgrp['interface']) + "\",\"" +
520 str(pcgrp['property']) + "\"," +
521 "static_cast<" +
522 str(pcgrp['type']).lower() + ">")
523 if isinstance(pcgrp['value'], str) or \
524 "string" in str(pcgrp['type']).lower():
525 value['value'] += ("(" + str(pcgrp['value']) + ")}")
526 else:
527 value['value'] += \
528 ("(" + str(pcgrp['value']).lower() + ")}")
529 values.append(value)
Matthew Barth9af190c2017-08-08 14:20:43 -0500530 param['values'] = values
531 params.append(param)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500532 pcs['params'] = params
533 pc.append(pcs)
Matthew Barth9af190c2017-08-08 14:20:43 -0500534 precond['pcact'] = pc
535
Matthew Barth7f272fd2017-09-12 16:16:56 -0500536 pcevents = []
537 for pce in event['precondition']['events']:
538 pcevent = getEvent(zNum, zCond, pce, events_data)
539 if not pcevent:
540 continue
541 pcevents.append(pcevent)
542 precond['pcevts'] = pcevents
543
Matthew Barthf20c3212018-03-02 14:42:55 -0600544 # Add precondition event triggers
545 precond['triggers'] = {}
546 for trig in event['precondition']['triggers']:
547 triggers = []
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600548 if (trig['name'] == "timer"):
549 precond['triggers']['pctime'] = getTimer(trig)
550 elif (trig['name'] == "signal"):
Matthew Barthf20c3212018-03-02 14:42:55 -0600551 if ('pcsigs' not in precond['triggers']):
552 precond['triggers']['pcsigs'] = []
553 triggers = getSignal(precond['pcgrps'], trig, events_data)
554 precond['triggers']['pcsigs'].extend(triggers)
Matthew Barth9af190c2017-08-08 14:20:43 -0500555
556 return precond
557
558
Gunnar Millsb751f322017-06-06 15:14:11 -0500559def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500560 """
561 Constructs the event entries defined for each zone using the events yaml
562 provided.
563 """
564 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500565
Matthew Barthd4d0f082017-05-16 13:51:10 -0500566 if 'events' in events_data:
567 for e in events_data['events']:
Matthew Barthd4d0f082017-05-16 13:51:10 -0500568 event = {}
Matthew Barth9af190c2017-08-08 14:20:43 -0500569 # Add precondition if given
570 if ('precondition' in e) and \
571 (e['precondition'] is not None):
Matthew Barth7f272fd2017-09-12 16:16:56 -0500572 event['pc'] = addPrecondition(zone_num,
573 zone_conditions,
574 e,
575 events_data)
Matthew Barth90149802017-08-15 10:51:37 -0500576 else:
Matthew Barth7f272fd2017-09-12 16:16:56 -0500577 event = getEvent(zone_num, zone_conditions, e, events_data)
578 if not event:
579 continue
Matthew Barthd4d0f082017-05-16 13:51:10 -0500580 events.append(event)
581
582 return events
583
584
Matt Spinler78498c92017-04-11 13:59:46 -0500585def getFansInZone(zone_num, profiles, fan_data):
586 """
587 Parses the fan definition YAML files to find the fans
588 that match both the zone passed in and one of the
589 cooling profiles.
590 """
591
592 fans = []
593
594 for f in fan_data['fans']:
595
596 if zone_num != f['cooling_zone']:
597 continue
598
Gunnar Mills67e95512017-06-02 14:35:18 -0500599 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500600 if f.get('cooling_profile') is None:
601 profile = "all"
602 else:
603 profile = f['cooling_profile']
604
605 if profile not in profiles:
606 continue
607
608 fan = {}
609 fan['name'] = f['inventory']
610 fan['sensors'] = f['sensors']
Lei YU069e4402018-01-31 16:47:37 +0800611 fan['target_interface'] = f.get(
612 'target_interface',
613 'xyz.openbmc_project.Control.FanSpeed')
Matt Spinler78498c92017-04-11 13:59:46 -0500614 fans.append(fan)
615
616 return fans
617
618
Matthew Barth7883f582019-02-14 14:24:46 -0600619def getIfacesInZone(zone_ifaces):
620 """
621 Parse given interfaces for a zone for associating a zone with an interface
622 and set any properties listed to defined values upon fan control starting
623 on the zone.
624 """
625
626 ifaces = []
627 for i in zone_ifaces:
628 iface = {}
629 # Interface name not needed yet for fan zones but
630 # may be necessary as more interfaces are extended by the zones
631 iface['name'] = i['name']
632
633 if ('properties' in i) and \
634 (i['properties'] is not None):
635 props = []
636 for p in i['properties']:
637 prop = {}
Matthew Barth59096e52019-02-18 12:23:38 -0600638 prop['name'] = p['name']
639 prop['func'] = str(p['name']).lower()
Matthew Barth7883f582019-02-14 14:24:46 -0600640 prop['type'] = parse_cpp_type(p['type'])
Matthew Barth59096e52019-02-18 12:23:38 -0600641 if ('persist' in p):
642 persist = p['persist']
643 if (persist is not None):
644 if (isinstance(persist, bool)):
645 prop['persist'] = 'true' if persist else 'false'
646 else:
647 prop['persist'] = 'false'
Matthew Barth7883f582019-02-14 14:24:46 -0600648 vals = []
649 for v in p['values']:
650 val = v['value']
651 if (val is not None):
652 if (isinstance(val, bool)):
653 # Convert True/False to 'true'/'false'
654 val = 'true' if val else 'false'
655 elif (isinstance(val, str)):
656 # Wrap strings with double-quotes
657 val = "\"" + val + "\""
658 vals.append(val)
659 prop['values'] = vals
660 props.append(prop)
661 iface['props'] = props
662 ifaces.append(iface)
663
664 return ifaces
665
666
Gunnar Millsee8a2812017-06-02 14:26:47 -0500667def getConditionInZoneConditions(zone_condition, zone_conditions_data):
668 """
669 Parses the zone conditions definition YAML files to find the condition
670 that match both the zone condition passed in.
671 """
672
673 condition = {}
674
675 for c in zone_conditions_data['conditions']:
676
677 if zone_condition != c['name']:
678 continue
679 condition['type'] = c['type']
680 properties = []
681 for p in c['properties']:
682 property = {}
683 property['property'] = p['property']
684 property['interface'] = p['interface']
685 property['path'] = p['path']
686 property['type'] = p['type'].lower()
687 property['value'] = str(p['value']).lower()
688 properties.append(property)
689 condition['properties'] = properties
690
691 return condition
692
693
694def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
Matt Spinler78498c92017-04-11 13:59:46 -0500695 """
696 Combines the zone definition YAML and fan
697 definition YAML to create a data structure defining
698 the fan cooling zones.
699 """
700
701 zone_groups = []
702
703 for group in zone_data:
704 conditions = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500705 # zone conditions are optional
706 if 'zone_conditions' in group and group['zone_conditions'] is not None:
707 for c in group['zone_conditions']:
708
709 if not zone_conditions_data:
Gunnar Millsb751f322017-06-06 15:14:11 -0500710 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500711 "zone_conditions used in zone YAML")
712
713 condition = getConditionInZoneConditions(c['name'],
714 zone_conditions_data)
715
716 if not condition:
717 sys.exit("Missing zone condition " + c['name'])
718
719 conditions.append(condition)
Matt Spinler78498c92017-04-11 13:59:46 -0500720
721 zone_group = {}
722 zone_group['conditions'] = conditions
723
724 zones = []
725 for z in group['zones']:
726 zone = {}
727
Gunnar Mills67e95512017-06-02 14:35:18 -0500728 # 'zone' is required
729 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500730 sys.exit("Missing fan zone number in " + zone_yaml)
731
732 zone['num'] = z['zone']
733
734 zone['full_speed'] = z['full_speed']
735
Matthew Barth1de66622017-06-12 13:13:02 -0500736 zone['default_floor'] = z['default_floor']
737
Matthew Bartha9561842017-06-29 11:43:45 -0500738 # 'increase_delay' is optional (use 0 by default)
739 key = 'increase_delay'
740 zone[key] = z.setdefault(key, 0)
741
742 # 'decrease_interval' is optional (use 0 by default)
743 key = 'decrease_interval'
744 zone[key] = z.setdefault(key, 0)
745
Gunnar Mills67e95512017-06-02 14:35:18 -0500746 # 'cooling_profiles' is optional (use 'all' instead)
747 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500748 (z['cooling_profiles'] is None):
749 profiles = ["all"]
750 else:
751 profiles = z['cooling_profiles']
752
Matthew Barth7883f582019-02-14 14:24:46 -0600753 # 'interfaces' is optional (no default)
Matthew Barth64099cd2019-02-18 09:43:12 -0600754 ifaces = []
Matthew Barth7883f582019-02-14 14:24:46 -0600755 if ('interfaces' in z) and \
756 (z['interfaces'] is not None):
757 ifaces = getIfacesInZone(z['interfaces'])
758
Matt Spinler78498c92017-04-11 13:59:46 -0500759 fans = getFansInZone(z['zone'], profiles, fan_data)
Gunnar Millsb751f322017-06-06 15:14:11 -0500760 events = getEventsInZone(z['zone'], group['zone_conditions'],
761 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500762
763 if len(fans) == 0:
764 sys.exit("Didn't find any fans in zone " + str(zone['num']))
765
Matthew Barth7883f582019-02-14 14:24:46 -0600766 if (ifaces):
767 zone['ifaces'] = ifaces
Matt Spinler78498c92017-04-11 13:59:46 -0500768 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500769 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500770 zones.append(zone)
771
772 zone_group['zones'] = zones
773 zone_groups.append(zone_group)
774
775 return zone_groups
776
777
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500778if __name__ == '__main__':
779 parser = ArgumentParser(
780 description="Phosphor fan zone definition parser")
781
782 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
783 default="example/zones.yaml",
784 help='fan zone definitional yaml')
785 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
786 default="example/fans.yaml",
787 help='fan definitional yaml')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500788 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
789 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500790 parser.add_argument('-c', '--zone_conditions_yaml',
791 dest='zone_conditions_yaml',
792 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500793 parser.add_argument('-o', '--output_dir', dest='output_dir',
794 default=".",
795 help='output directory')
796 args = parser.parse_args()
797
798 if not args.zone_yaml or not args.fan_yaml:
799 parser.print_usage()
William A. Kennington III3e781062018-10-19 17:18:34 -0700800 sys.exit(1)
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500801
802 with open(args.zone_yaml, 'r') as zone_input:
803 zone_data = yaml.safe_load(zone_input) or {}
804
805 with open(args.fan_yaml, 'r') as fan_input:
806 fan_data = yaml.safe_load(fan_input) or {}
807
Matthew Barthd4d0f082017-05-16 13:51:10 -0500808 events_data = {}
809 if args.events_yaml:
810 with open(args.events_yaml, 'r') as events_input:
811 events_data = yaml.safe_load(events_input) or {}
812
Gunnar Millsee8a2812017-06-02 14:26:47 -0500813 zone_conditions_data = {}
814 if args.zone_conditions_yaml:
815 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
816 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
817
Matt Spinleree7f6422017-05-09 11:03:14 -0500818 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500819 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500820
821 manager_config = zone_data.get('manager_configuration', {})
822
823 if manager_config.get('power_on_delay') is None:
824 manager_config['power_on_delay'] = 0
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500825
Matthew Barth702c4a52018-02-28 16:23:11 -0600826 tmpls_dir = os.path.join(
827 os.path.dirname(os.path.realpath(__file__)),
828 "templates")
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500829 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
Matthew Barth702c4a52018-02-28 16:23:11 -0600830 if sys.version_info < (3, 0):
831 lkup = TemplateLookup(
832 directories=tmpls_dir.split(),
833 disable_unicode=True)
834 else:
835 lkup = TemplateLookup(
836 directories=tmpls_dir.split())
837 tmpl = lkup.get_template('fan_zone_defs.mako.cpp')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500838 with open(output_file, 'w') as output:
Matthew Barth702c4a52018-02-28 16:23:11 -0600839 output.write(tmpl.render(zones=zone_config,
840 mgr_data=manager_config))