blob: f9eb1adb88bcad2782871106a4733b1a040609e6 [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 Barth1b4de262018-03-06 13:03:16 -0600171 e += "\t}))\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600172 e += "},\n"
173
174 e += "std::vector<Signal>{\n"
Matthew Bartha69465a2018-03-02 13:50:59 -0600175 for s in event['triggers']['signals']:
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600176 e += "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"
187 else:
188 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")
194 else:
195 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 += "},\n"
204 e += "}\n"
205
206 e += "}"
207
208 return e
209
210
Matthew Barth6c050692017-12-05 15:30:09 -0600211def getGroups(zNum, zCond, edata, events):
212 """
213 Extract and construct the groups for the given event.
214 """
215 groups = []
216 for eGroups in edata['groups']:
217 if ('zone_conditions' in eGroups) and \
218 (eGroups['zone_conditions'] is not None):
219 # Zone conditions are optional in the events yaml but skip
220 # if this event's condition is not in this zone's conditions
221 if all('name' in z and z['name'] is not None and
222 not any(c['name'] == z['name'] for c in zCond)
223 for z in eGroups['zone_conditions']):
224 continue
225
226 # Zone numbers are optional in the events yaml but skip if this
227 # zone's zone number is not in the event's zone numbers
228 if all('zones' in z and z['zones'] is not None and
229 zNum not in z['zones']
230 for z in eGroups['zone_conditions']):
231 continue
Matthew Barth6c050692017-12-05 15:30:09 -0600232 eGroup = next(g for g in events['groups']
233 if g['name'] == eGroups['name'])
234
235 group = {}
236 members = []
237 group['name'] = eGroup['name']
238 for m in eGroup['members']:
239 member = {}
240 member['path'] = eGroup['type']
241 member['object'] = (eGroup['type'] + m)
242 member['interface'] = eGroups['interface']
243 member['property'] = eGroups['property']['name']
244 member['type'] = eGroups['property']['type']
Matthew Barth18c91032019-01-29 15:36:00 -0600245 # Use defined service to note member on zone object
246 if ('service' in eGroup) and \
247 (eGroup['service'] is not None):
248 member['service'] = eGroup['service']
Matthew Barth6c050692017-12-05 15:30:09 -0600249 # Add expected group member's property value if given
250 if ('value' in eGroups['property']) and \
251 (eGroups['property']['value'] is not None):
252 if isinstance(eGroups['property']['value'], str) or \
253 "string" in str(member['type']).lower():
254 member['value'] = (
255 "\"" + eGroups['property']['value'] + "\"")
256 else:
257 member['value'] = eGroups['property']['value']
258 members.append(member)
259 group['members'] = members
260 groups.append(group)
261 return groups
262
263
Matthew Bartha69465a2018-03-02 13:50:59 -0600264def getSignal(eGrps, eTrig, events):
265 """
266 Extracts and constructs for each group member a signal
267 subscription of each match listed in the trigger.
268 """
269 signals = []
270 for group in eGrps:
271 for member in group['members']:
272 for eMatches in eTrig['matches']:
273 signal = {}
274 eMatch = next(m for m in events['matches']
275 if m['name'] == eMatches['name'])
276 # If service not given, subscribe to signal match
277 if ('service' not in member):
278 signal['match'] = eMatch['name']
279 params = []
280 if ('parameters' in eMatch) and \
281 (eMatch['parameters'] is not None):
282 for p in eMatch['parameters']:
283 params.append(member[str(p)])
284 signal['mparams'] = params
285
286 if ('parameters' in eMatch['signal']) and \
287 (eMatch['signal']['parameters'] is not None):
288 eSignal = eMatch['signal']
289 else:
290 eSignal = next(s for s in events['signals']
291 if s['name'] == eMatch['signal'])
292 signal['signal'] = eSignal['name']
293 sparams = {}
294 if ('parameters' in eSignal) and \
295 (eSignal['parameters'] is not None):
296 splist = []
297 for p in eSignal['parameters']:
298 sp = str(p)
299 if (sp != 'type'):
300 splist.append(sp)
301 if (sp != 'group'):
302 sparams[sp] = "\"" + member[sp] + "\""
303 else:
304 sparams[sp] = "Group{\n"
305 for m in group['members']:
306 sparams[sp] += (
307 "{\n" +
308 "\"" + str(m['object']) + "\",\n" +
309 "{\"" + str(m['interface']) + "\"," +
310 "\"" + str(m['property']) + "\"}\n" +
311 "},\n")
312 sparams[sp] += "}"
313 else:
314 sparams[sp] = member[sp]
315 sparams['params'] = splist
316 signal['sparams'] = sparams
317 # Add signal handler
318 eHandler = next(h for h in events['handlers']
319 if h['name'] == eSignal['handler'])
320 signal['handler'] = eHandler['name']
321 hparams = {}
322 if ('parameters' in eHandler) and \
323 (eHandler['parameters'] is not None):
324 hplist = []
325 for p in eHandler['parameters']:
326 hp = str(p)
327 if (hp != 'type'):
328 hplist.append(hp)
329 if (hp != 'group'):
330 hparams[hp] = "\"" + member[hp] + "\""
331 else:
332 hparams[hp] = "Group{\n"
333 for m in group['members']:
334 hparams[hp] += (
335 "{\n" +
336 "\"" + str(m['object']) + "\",\n" +
337 "{\"" + str(m['interface']) + "\"," +
338 "\"" + str(m['property']) + "\"}\n" +
339 "},\n")
340 hparams[hp] += "}"
341 else:
342 hparams[hp] = member[hp]
343 hparams['params'] = hplist
344 signal['hparams'] = hparams
345 signals.append(signal)
346 return signals
347
348
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600349def getTimer(eTrig):
350 """
351 Extracts and constructs the required parameters for an
352 event timer.
353 """
354 timer = {}
355 timer['interval'] = (
356 "static_cast<std::chrono::microseconds>" +
357 "(" + str(eTrig['interval']) + ")")
358 timer['type'] = "TimerType::" + str(eTrig['type'])
359 return timer
360
361
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600362def getActions(zNum, zCond, edata, actions, events):
Matthew Barth9df74752017-10-11 14:39:31 -0500363 """
364 Extracts and constructs the make_action function call for
365 all the actions within the given event.
366 """
367 action = []
368 for eActions in actions['actions']:
369 actions = {}
370 eAction = next(a for a in events['actions']
371 if a['name'] == eActions['name'])
372 actions['name'] = eAction['name']
373 params = []
374 if ('parameters' in eAction) and \
375 (eAction['parameters'] is not None):
376 for p in eAction['parameters']:
377 param = "static_cast<"
378 if type(eActions[p]) is not dict:
379 if p == 'actions':
380 param = "std::vector<Action>{"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600381 pActs = getActions(zNum,
382 zCond,
383 edata,
384 eActions,
385 events)
Matthew Barth9df74752017-10-11 14:39:31 -0500386 for a in pActs:
387 if (len(a['parameters']) != 0):
388 param += (
389 "make_action(action::" +
390 a['name'] +
391 "(\n")
392 for i, ap in enumerate(a['parameters']):
393 if (i+1) != len(a['parameters']):
394 param += (ap + ",")
395 else:
396 param += (ap + ")")
397 else:
398 param += ("make_action(action::" + a['name'])
399 param += "),"
400 param += "}"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600401 elif p == 'defevents' or p == 'altevents':
402 param = "std::vector<SetSpeedEvent>{\n"
403 for i, e in enumerate(eActions[p]):
404 aEvent = getEvent(zNum, zCond, e, events)
405 if not aEvent:
406 continue
407 if (i+1) != len(eActions[p]):
408 param += genEvent(aEvent) + ",\n"
409 else:
410 param += genEvent(aEvent) + "\n"
411 param += "\t}"
Matthew Barth9df74752017-10-11 14:39:31 -0500412 elif p == 'property':
Matthew Barth9a5b6992018-01-23 15:32:26 -0600413 if isinstance(eActions[p], str) or \
Matthew Barth6c050692017-12-05 15:30:09 -0600414 "string" in str(eActions[p]['type']).lower():
Matthew Barth9a5b6992018-01-23 15:32:26 -0600415 param += (
Matthew Barth6c050692017-12-05 15:30:09 -0600416 str(eActions[p]['type']).lower() +
Matthew Barth9a5b6992018-01-23 15:32:26 -0600417 ">(\"" + str(eActions[p]) + "\")")
418 else:
419 param += (
Matthew Barth6c050692017-12-05 15:30:09 -0600420 str(eActions[p]['type']).lower() +
421 ">(" + str(eActions[p]['value']).lower() + ")")
Matthew Barth9df74752017-10-11 14:39:31 -0500422 else:
423 # Default type to 'size_t' when not given
424 param += ("size_t>(" + str(eActions[p]).lower() + ")")
425 else:
426 if p == 'timer':
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600427 t = getTimer(eActions[p])
Matthew Barth9df74752017-10-11 14:39:31 -0500428 param = (
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600429 "TimerConf{" + t['interval'] + "," +
430 t['type'] + "}")
Matthew Barth9df74752017-10-11 14:39:31 -0500431 else:
432 param += (str(eActions[p]['type']).lower() + ">(")
433 if p != 'map':
Matthew Barth9a5b6992018-01-23 15:32:26 -0600434 if isinstance(eActions[p]['value'], str) or \
435 "string" in str(eActions[p]['type']).lower():
436 param += \
437 "\"" + str(eActions[p]['value']) + "\")"
438 else:
439 param += \
440 str(eActions[p]['value']).lower() + ")"
Matthew Barth9df74752017-10-11 14:39:31 -0500441 else:
442 param += (
443 str(eActions[p]['type']).lower() +
444 convertToMap(str(eActions[p]['value'])) + ")")
445 params.append(param)
446 actions['parameters'] = params
447 action.append(actions)
448 return action
449
450
Matthew Barth7f272fd2017-09-12 16:16:56 -0500451def getEvent(zone_num, zone_conditions, e, events_data):
452 """
453 Parses the sections of an event and populates the properties
454 that construct an event within the generated source.
455 """
456 event = {}
Matthew Barth7f272fd2017-09-12 16:16:56 -0500457
Matthew Barth6c050692017-12-05 15:30:09 -0600458 # Add set speed event groups
459 grps = getGroups(zone_num, zone_conditions, e, events_data)
460 if not grps:
Matthew Barth7f272fd2017-09-12 16:16:56 -0500461 return
Matthew Barth6c050692017-12-05 15:30:09 -0600462 event['groups'] = grps
Matthew Barth7f272fd2017-09-12 16:16:56 -0500463
Matthew Barthe3d1c4a2018-01-11 13:53:49 -0600464 # Add optional set speed actions and function parameters
465 event['action'] = []
466 if ('actions' in e) and \
467 (e['actions'] is not None):
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600468 event['action'] = getActions(zone_num,
469 zone_conditions,
470 e,
471 e,
472 events_data)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500473
Matthew Bartha69465a2018-03-02 13:50:59 -0600474 # Add event triggers
475 event['triggers'] = {}
476 for trig in e['triggers']:
477 triggers = []
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600478 if (trig['name'] == "timer"):
479 event['triggers']['timer'] = getTimer(trig)
480 elif (trig['name'] == "signal"):
Matthew Bartha69465a2018-03-02 13:50:59 -0600481 if ('signals' not in event['triggers']):
482 event['triggers']['signals'] = []
483 triggers = getSignal(event['groups'], trig, events_data)
484 event['triggers']['signals'].extend(triggers)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500485
Matthew Barth7f272fd2017-09-12 16:16:56 -0500486 return event
487
488
489def addPrecondition(zNum, zCond, event, events_data):
Matthew Barth9af190c2017-08-08 14:20:43 -0500490 """
491 Parses the precondition section of an event and populates the necessary
492 structures to generate a precondition for a set speed event.
493 """
494 precond = {}
495 # Add set speed event precondition group
Matthew Barth6c050692017-12-05 15:30:09 -0600496 grps = getGroups(zNum, zCond, event['precondition'], events_data)
497 if not grps:
498 return
499 precond['pcgrps'] = grps
Matthew Barth9af190c2017-08-08 14:20:43 -0500500
Matthew Barth7f272fd2017-09-12 16:16:56 -0500501 # Add set speed event precondition actions
502 pc = []
503 pcs = {}
504 pcs['name'] = event['precondition']['name']
505 epc = next(p for p in events_data['preconditions']
Matthew Barth9af190c2017-08-08 14:20:43 -0500506 if p['name'] == event['precondition']['name'])
507 params = []
Matthew Barth7f272fd2017-09-12 16:16:56 -0500508 for p in epc['parameters']:
Matthew Barth9af190c2017-08-08 14:20:43 -0500509 param = {}
510 if p == 'groups':
511 param['type'] = "std::vector<PrecondGroup>"
512 param['open'] = "{"
513 param['close'] = "}"
514 values = []
Matthew Barth6c050692017-12-05 15:30:09 -0600515 for group in precond['pcgrps']:
516 for pcgrp in group['members']:
517 value = {}
518 value['value'] = (
519 "PrecondGroup{\"" +
520 str(pcgrp['object']) + "\",\"" +
521 str(pcgrp['interface']) + "\",\"" +
522 str(pcgrp['property']) + "\"," +
523 "static_cast<" +
524 str(pcgrp['type']).lower() + ">")
525 if isinstance(pcgrp['value'], str) or \
526 "string" in str(pcgrp['type']).lower():
527 value['value'] += ("(" + str(pcgrp['value']) + ")}")
528 else:
529 value['value'] += \
530 ("(" + str(pcgrp['value']).lower() + ")}")
531 values.append(value)
Matthew Barth9af190c2017-08-08 14:20:43 -0500532 param['values'] = values
533 params.append(param)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500534 pcs['params'] = params
535 pc.append(pcs)
Matthew Barth9af190c2017-08-08 14:20:43 -0500536 precond['pcact'] = pc
537
Matthew Barth7f272fd2017-09-12 16:16:56 -0500538 pcevents = []
539 for pce in event['precondition']['events']:
540 pcevent = getEvent(zNum, zCond, pce, events_data)
541 if not pcevent:
542 continue
543 pcevents.append(pcevent)
544 precond['pcevts'] = pcevents
545
Matthew Barthf20c3212018-03-02 14:42:55 -0600546 # Add precondition event triggers
547 precond['triggers'] = {}
548 for trig in event['precondition']['triggers']:
549 triggers = []
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600550 if (trig['name'] == "timer"):
551 precond['triggers']['pctime'] = getTimer(trig)
552 elif (trig['name'] == "signal"):
Matthew Barthf20c3212018-03-02 14:42:55 -0600553 if ('pcsigs' not in precond['triggers']):
554 precond['triggers']['pcsigs'] = []
555 triggers = getSignal(precond['pcgrps'], trig, events_data)
556 precond['triggers']['pcsigs'].extend(triggers)
Matthew Barth9af190c2017-08-08 14:20:43 -0500557
558 return precond
559
560
Gunnar Millsb751f322017-06-06 15:14:11 -0500561def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500562 """
563 Constructs the event entries defined for each zone using the events yaml
564 provided.
565 """
566 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500567
Matthew Barthd4d0f082017-05-16 13:51:10 -0500568 if 'events' in events_data:
569 for e in events_data['events']:
Matthew Barthd4d0f082017-05-16 13:51:10 -0500570 event = {}
Matthew Barth9af190c2017-08-08 14:20:43 -0500571 # Add precondition if given
572 if ('precondition' in e) and \
573 (e['precondition'] is not None):
Matthew Barth7f272fd2017-09-12 16:16:56 -0500574 event['pc'] = addPrecondition(zone_num,
575 zone_conditions,
576 e,
577 events_data)
Matthew Barth90149802017-08-15 10:51:37 -0500578 else:
Matthew Barth7f272fd2017-09-12 16:16:56 -0500579 event = getEvent(zone_num, zone_conditions, e, events_data)
580 if not event:
581 continue
Matthew Barthd4d0f082017-05-16 13:51:10 -0500582 events.append(event)
583
584 return events
585
586
Matt Spinler78498c92017-04-11 13:59:46 -0500587def getFansInZone(zone_num, profiles, fan_data):
588 """
589 Parses the fan definition YAML files to find the fans
590 that match both the zone passed in and one of the
591 cooling profiles.
592 """
593
594 fans = []
595
596 for f in fan_data['fans']:
597
598 if zone_num != f['cooling_zone']:
599 continue
600
Gunnar Mills67e95512017-06-02 14:35:18 -0500601 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500602 if f.get('cooling_profile') is None:
603 profile = "all"
604 else:
605 profile = f['cooling_profile']
606
607 if profile not in profiles:
608 continue
609
610 fan = {}
611 fan['name'] = f['inventory']
612 fan['sensors'] = f['sensors']
Lei YU069e4402018-01-31 16:47:37 +0800613 fan['target_interface'] = f.get(
614 'target_interface',
615 'xyz.openbmc_project.Control.FanSpeed')
Matt Spinler78498c92017-04-11 13:59:46 -0500616 fans.append(fan)
617
618 return fans
619
620
Matthew Barth7883f582019-02-14 14:24:46 -0600621def getIfacesInZone(zone_ifaces):
622 """
623 Parse given interfaces for a zone for associating a zone with an interface
624 and set any properties listed to defined values upon fan control starting
625 on the zone.
626 """
627
628 ifaces = []
629 for i in zone_ifaces:
630 iface = {}
631 # Interface name not needed yet for fan zones but
632 # may be necessary as more interfaces are extended by the zones
633 iface['name'] = i['name']
634
635 if ('properties' in i) and \
636 (i['properties'] is not None):
637 props = []
638 for p in i['properties']:
639 prop = {}
Matthew Barth59096e52019-02-18 12:23:38 -0600640 prop['name'] = p['name']
641 prop['func'] = str(p['name']).lower()
Matthew Barth7883f582019-02-14 14:24:46 -0600642 prop['type'] = parse_cpp_type(p['type'])
Matthew Barth59096e52019-02-18 12:23:38 -0600643 if ('persist' in p):
644 persist = p['persist']
645 if (persist is not None):
646 if (isinstance(persist, bool)):
647 prop['persist'] = 'true' if persist else 'false'
648 else:
649 prop['persist'] = 'false'
Matthew Barth7883f582019-02-14 14:24:46 -0600650 vals = []
651 for v in p['values']:
652 val = v['value']
653 if (val is not None):
654 if (isinstance(val, bool)):
655 # Convert True/False to 'true'/'false'
656 val = 'true' if val else 'false'
657 elif (isinstance(val, str)):
658 # Wrap strings with double-quotes
659 val = "\"" + val + "\""
660 vals.append(val)
661 prop['values'] = vals
662 props.append(prop)
663 iface['props'] = props
664 ifaces.append(iface)
665
666 return ifaces
667
668
Gunnar Millsee8a2812017-06-02 14:26:47 -0500669def getConditionInZoneConditions(zone_condition, zone_conditions_data):
670 """
671 Parses the zone conditions definition YAML files to find the condition
672 that match both the zone condition passed in.
673 """
674
675 condition = {}
676
677 for c in zone_conditions_data['conditions']:
678
679 if zone_condition != c['name']:
680 continue
681 condition['type'] = c['type']
682 properties = []
683 for p in c['properties']:
684 property = {}
685 property['property'] = p['property']
686 property['interface'] = p['interface']
687 property['path'] = p['path']
688 property['type'] = p['type'].lower()
689 property['value'] = str(p['value']).lower()
690 properties.append(property)
691 condition['properties'] = properties
692
693 return condition
694
695
696def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
Matt Spinler78498c92017-04-11 13:59:46 -0500697 """
698 Combines the zone definition YAML and fan
699 definition YAML to create a data structure defining
700 the fan cooling zones.
701 """
702
703 zone_groups = []
704
705 for group in zone_data:
706 conditions = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500707 # zone conditions are optional
708 if 'zone_conditions' in group and group['zone_conditions'] is not None:
709 for c in group['zone_conditions']:
710
711 if not zone_conditions_data:
Gunnar Millsb751f322017-06-06 15:14:11 -0500712 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500713 "zone_conditions used in zone YAML")
714
715 condition = getConditionInZoneConditions(c['name'],
716 zone_conditions_data)
717
718 if not condition:
719 sys.exit("Missing zone condition " + c['name'])
720
721 conditions.append(condition)
Matt Spinler78498c92017-04-11 13:59:46 -0500722
723 zone_group = {}
724 zone_group['conditions'] = conditions
725
726 zones = []
727 for z in group['zones']:
728 zone = {}
729
Gunnar Mills67e95512017-06-02 14:35:18 -0500730 # 'zone' is required
731 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500732 sys.exit("Missing fan zone number in " + zone_yaml)
733
734 zone['num'] = z['zone']
735
736 zone['full_speed'] = z['full_speed']
737
Matthew Barth1de66622017-06-12 13:13:02 -0500738 zone['default_floor'] = z['default_floor']
739
Matthew Bartha9561842017-06-29 11:43:45 -0500740 # 'increase_delay' is optional (use 0 by default)
741 key = 'increase_delay'
742 zone[key] = z.setdefault(key, 0)
743
744 # 'decrease_interval' is optional (use 0 by default)
745 key = 'decrease_interval'
746 zone[key] = z.setdefault(key, 0)
747
Gunnar Mills67e95512017-06-02 14:35:18 -0500748 # 'cooling_profiles' is optional (use 'all' instead)
749 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500750 (z['cooling_profiles'] is None):
751 profiles = ["all"]
752 else:
753 profiles = z['cooling_profiles']
754
Matthew Barth7883f582019-02-14 14:24:46 -0600755 # 'interfaces' is optional (no default)
Matthew Barth64099cd2019-02-18 09:43:12 -0600756 ifaces = []
Matthew Barth7883f582019-02-14 14:24:46 -0600757 if ('interfaces' in z) and \
758 (z['interfaces'] is not None):
759 ifaces = getIfacesInZone(z['interfaces'])
760
Matt Spinler78498c92017-04-11 13:59:46 -0500761 fans = getFansInZone(z['zone'], profiles, fan_data)
Gunnar Millsb751f322017-06-06 15:14:11 -0500762 events = getEventsInZone(z['zone'], group['zone_conditions'],
763 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500764
765 if len(fans) == 0:
766 sys.exit("Didn't find any fans in zone " + str(zone['num']))
767
Matthew Barth7883f582019-02-14 14:24:46 -0600768 if (ifaces):
769 zone['ifaces'] = ifaces
Matt Spinler78498c92017-04-11 13:59:46 -0500770 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500771 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500772 zones.append(zone)
773
774 zone_group['zones'] = zones
775 zone_groups.append(zone_group)
776
777 return zone_groups
778
779
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500780if __name__ == '__main__':
781 parser = ArgumentParser(
782 description="Phosphor fan zone definition parser")
783
784 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
785 default="example/zones.yaml",
786 help='fan zone definitional yaml')
787 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
788 default="example/fans.yaml",
789 help='fan definitional yaml')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500790 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
791 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500792 parser.add_argument('-c', '--zone_conditions_yaml',
793 dest='zone_conditions_yaml',
794 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500795 parser.add_argument('-o', '--output_dir', dest='output_dir',
796 default=".",
797 help='output directory')
798 args = parser.parse_args()
799
800 if not args.zone_yaml or not args.fan_yaml:
801 parser.print_usage()
William A. Kennington III3e781062018-10-19 17:18:34 -0700802 sys.exit(1)
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500803
804 with open(args.zone_yaml, 'r') as zone_input:
805 zone_data = yaml.safe_load(zone_input) or {}
806
807 with open(args.fan_yaml, 'r') as fan_input:
808 fan_data = yaml.safe_load(fan_input) or {}
809
Matthew Barthd4d0f082017-05-16 13:51:10 -0500810 events_data = {}
811 if args.events_yaml:
812 with open(args.events_yaml, 'r') as events_input:
813 events_data = yaml.safe_load(events_input) or {}
814
Gunnar Millsee8a2812017-06-02 14:26:47 -0500815 zone_conditions_data = {}
816 if args.zone_conditions_yaml:
817 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
818 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
819
Matt Spinleree7f6422017-05-09 11:03:14 -0500820 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500821 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500822
823 manager_config = zone_data.get('manager_configuration', {})
824
825 if manager_config.get('power_on_delay') is None:
826 manager_config['power_on_delay'] = 0
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500827
Matthew Barth702c4a52018-02-28 16:23:11 -0600828 tmpls_dir = os.path.join(
829 os.path.dirname(os.path.realpath(__file__)),
830 "templates")
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500831 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
Matthew Barth702c4a52018-02-28 16:23:11 -0600832 if sys.version_info < (3, 0):
833 lkup = TemplateLookup(
834 directories=tmpls_dir.split(),
835 disable_unicode=True)
836 else:
837 lkup = TemplateLookup(
838 directories=tmpls_dir.split())
839 tmpl = lkup.get_template('fan_zone_defs.mako.cpp')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500840 with open(output_file, 'w') as output:
Matthew Barth702c4a52018-02-28 16:23:11 -0600841 output.write(tmpl.render(zones=zone_config,
842 mgr_data=manager_config))