blob: e0855702d834fdd06cedae6e30a3c1ac187f9078 [file] [log] [blame]
Matthew Barthf24d7742020-03-17 16:12:15 -05001#!/usr/bin/env python3
Matt Spinlerd08dbe22017-04-11 13:52:54 -05002
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:
Matthew Barth867a31c2020-02-13 13:13:44 -0600117 result += ", ".join([get_cpp_type(e) for e in rest])
Matthew Barth7883f582019-02-14 14:24:46 -0600118 result += '>'
119
120 return result
121
122
Matthew Barthbb12c922017-06-13 13:57:40 -0500123def convertToMap(listOfDict):
124 """
125 Converts a list of dictionary entries to a std::map initialization list.
126 """
Matthew Barth9a5b6992018-01-23 15:32:26 -0600127 listOfDict = listOfDict.replace('\'', '\"')
Matthew Barthbb12c922017-06-13 13:57:40 -0500128 listOfDict = listOfDict.replace('[', '{')
129 listOfDict = listOfDict.replace(']', '}')
130 listOfDict = listOfDict.replace(':', ',')
131 return listOfDict
132
133
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600134def genEvent(event):
135 """
136 Generates the source code of an event and returns it as a string
137 """
138 e = "SetSpeedEvent{\n"
Matthew Barth621a5772018-11-14 14:55:11 -0600139 e += "\"" + event['name'] + "\",\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600140 e += "Group{\n"
141 for group in event['groups']:
142 for member in group['members']:
Matthew Barth06fa7812018-11-20 09:54:30 -0600143 e += "{\"" + member['object'] + "\",\n"
Matthew Barth146b7392018-03-08 16:17:58 -0600144 e += "\"" + member['interface'] + "\",\n"
Matthew Barth06fa7812018-11-20 09:54:30 -0600145 e += "\"" + member['property'] + "\"},\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600146 e += "},\n"
147
Matthew Barth06fa7812018-11-20 09:54:30 -0600148 e += "ActionData{\n"
149 for d in event['action']:
150 e += "{Group{\n"
151 for g in d['groups']:
152 for m in g['members']:
Matthew Barth6db547d2019-10-08 14:57:22 -0500153 e += "{\"" + m['object'] + "\",\n"
154 e += "\"" + m['interface'] + "\",\n"
155 e += "\"" + m['property'] + "\"},\n"
Matthew Barth06fa7812018-11-20 09:54:30 -0600156 e += "},\n"
157 e += "std::vector<Action>{\n"
158 for a in d['actions']:
Matthew Barth8a697b62018-12-14 13:23:47 -0600159 if len(a['parameters']) != 0:
160 e += "make_action(action::" + a['name'] + "(\n"
161 else:
162 e += "make_action(action::" + a['name'] + "\n"
Matthew Barth06fa7812018-11-20 09:54:30 -0600163 for i, p in enumerate(a['parameters']):
164 if (i+1) != len(a['parameters']):
165 e += p + ",\n"
166 else:
167 e += p + "\n"
Matthew Barth8a697b62018-12-14 13:23:47 -0600168 if len(a['parameters']) != 0:
169 e += ")),\n"
170 else:
171 e += "),\n"
Matthew Barth06fa7812018-11-20 09:54:30 -0600172 e += "}},\n"
173 e += "},\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600174
Matthew Barth1b4de262018-03-06 13:03:16 -0600175 e += "std::vector<Trigger>{\n"
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600176 if ('timer' in event['triggers']) and \
177 (event['triggers']['timer'] is not None):
Matthew Barth1b4de262018-03-06 13:03:16 -0600178 e += "\tmake_trigger(trigger::timer(TimerConf{\n"
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600179 e += "\t" + event['triggers']['timer']['interval'] + ",\n"
180 e += "\t" + event['triggers']['timer']['type'] + "\n"
Matthew Barth016bd242018-03-07 16:06:06 -0600181 e += "\t})),\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600182
Matthew Barth016bd242018-03-07 16:06:06 -0600183 if ('signals' in event['triggers']) and \
184 (event['triggers']['signals'] is not None):
185 for s in event['triggers']['signals']:
186 e += "\tmake_trigger(trigger::signal(\n"
187 e += "match::" + s['match'] + "(\n"
Matthew Barth926df662018-10-09 09:51:12 -0500188 for i, mp in enumerate(s['mparams']['params']):
189 if (i+1) != len(s['mparams']['params']):
190 e += "\t\t\t" + s['mparams'][mp] + ",\n"
Matthew Barth016bd242018-03-07 16:06:06 -0600191 else:
Matthew Barth926df662018-10-09 09:51:12 -0500192 e += "\t\t\t" + s['mparams'][mp] + "\n"
193 e += "\t\t),\n"
194 e += "\t\tmake_handler<SignalHandler>(\n"
Matthew Barth016bd242018-03-07 16:06:06 -0600195 if ('type' in s['sparams']) and (s['sparams']['type'] is not None):
196 e += s['signal'] + "<" + s['sparams']['type'] + ">(\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600197 else:
Matthew Barth016bd242018-03-07 16:06:06 -0600198 e += s['signal'] + "(\n"
199 for sp in s['sparams']['params']:
200 e += s['sparams'][sp] + ",\n"
201 if ('type' in s['hparams']) and (s['hparams']['type'] is not None):
202 e += ("handler::" + s['handler'] +
203 "<" + s['hparams']['type'] + ">(\n")
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600204 else:
Matthew Barth016bd242018-03-07 16:06:06 -0600205 e += "handler::" + s['handler'] + "(\n)"
206 for i, hp in enumerate(s['hparams']['params']):
207 if (i+1) != len(s['hparams']['params']):
208 e += s['hparams'][hp] + ",\n"
209 else:
210 e += s['hparams'][hp] + "\n"
211 e += "))\n"
Matthew Barth926df662018-10-09 09:51:12 -0500212 e += "\t\t)\n"
Matthew Barth016bd242018-03-07 16:06:06 -0600213 e += "\t)),\n"
214
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600215 if ('init' in event['triggers']):
216 for i in event['triggers']['init']:
217 e += "\tmake_trigger(trigger::init(\n"
Matthew Barth926df662018-10-09 09:51:12 -0500218 if ('method' in i):
219 e += "\t\tmake_handler<MethodHandler>(\n"
220 if ('type' in i['mparams']) and \
221 (i['mparams']['type'] is not None):
222 e += i['method'] + "<" + i['mparams']['type'] + ">(\n"
223 else:
224 e += i['method'] + "(\n"
225 for ip in i['mparams']['params']:
226 e += i['mparams'][ip] + ",\n"
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600227 if ('type' in i['hparams']) and \
228 (i['hparams']['type'] is not None):
229 e += ("handler::" + i['handler'] +
230 "<" + i['hparams']['type'] + ">(\n")
231 else:
232 e += "handler::" + i['handler'] + "(\n)"
233 for i, hp in enumerate(i['hparams']['params']):
234 if (i+1) != len(i['hparams']['params']):
235 e += i['hparams'][hp] + ",\n"
236 else:
237 e += i['hparams'][hp] + "\n"
Matthew Barth926df662018-10-09 09:51:12 -0500238 e += "))\n"
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600239 e += "\t\t)\n"
240 e += "\t)),\n"
241
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600242 e += "},\n"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600243
244 e += "}"
245
246 return e
247
248
Matthew Barth6c050692017-12-05 15:30:09 -0600249def getGroups(zNum, zCond, edata, events):
250 """
251 Extract and construct the groups for the given event.
252 """
253 groups = []
Matthew Barthf7e1cb32018-11-19 11:19:09 -0600254 if ('groups' in edata) and (edata['groups'] is not None):
255 for eGroups in edata['groups']:
256 if ('zone_conditions' in eGroups) and \
257 (eGroups['zone_conditions'] is not None):
258 # Zone conditions are optional in the events yaml but skip
259 # if this event's condition is not in this zone's conditions
260 if all('name' in z and z['name'] is not None and
261 not any(c['name'] == z['name'] for c in zCond)
262 for z in eGroups['zone_conditions']):
263 continue
Matthew Barth6c050692017-12-05 15:30:09 -0600264
Matthew Barthf7e1cb32018-11-19 11:19:09 -0600265 # Zone numbers are optional in the events yaml but skip if this
266 # zone's zone number is not in the event's zone numbers
267 if all('zones' in z and z['zones'] is not None and
268 zNum not in z['zones']
269 for z in eGroups['zone_conditions']):
270 continue
271 eGroup = next(g for g in events['groups']
272 if g['name'] == eGroups['name'])
Matthew Barth6c050692017-12-05 15:30:09 -0600273
Matthew Barthf7e1cb32018-11-19 11:19:09 -0600274 group = {}
275 members = []
276 group['name'] = eGroup['name']
277 for m in eGroup['members']:
278 member = {}
279 member['path'] = eGroup['type']
280 member['object'] = (eGroup['type'] + m)
281 member['interface'] = eGroups['interface']
282 member['property'] = eGroups['property']['name']
283 member['type'] = eGroups['property']['type']
284 # Use defined service to note member on zone object
285 if ('service' in eGroup) and \
286 (eGroup['service'] is not None):
287 member['service'] = eGroup['service']
288 # Add expected group member's property value if given
289 if ('value' in eGroups['property']) and \
290 (eGroups['property']['value'] is not None):
291 if isinstance(eGroups['property']['value'], str) or \
292 "string" in str(member['type']).lower():
293 member['value'] = (
294 "\"" + eGroups['property']['value'] + "\"")
295 else:
296 member['value'] = eGroups['property']['value']
297 members.append(member)
298 group['members'] = members
299 groups.append(group)
Matthew Barth6c050692017-12-05 15:30:09 -0600300 return groups
301
302
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600303def getParameters(member, groups, section, events):
Matthew Barth73379f92018-03-15 11:37:10 -0500304 """
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600305 Extracts and constructs a section's parameters
Matthew Barth73379f92018-03-15 11:37:10 -0500306 """
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600307 params = {}
308 if ('parameters' in section) and \
309 (section['parameters'] is not None):
310 plist = []
311 for sp in section['parameters']:
312 p = str(sp)
313 if (p != 'type'):
314 plist.append(p)
315 if (p != 'group'):
316 params[p] = "\"" + member[p] + "\""
Matthew Barth73379f92018-03-15 11:37:10 -0500317 else:
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600318 params[p] = "Group\n{\n"
319 for g in groups:
320 for m in g['members']:
321 params[p] += (
322 "{\"" + str(m['object']) + "\",\n" +
323 "\"" + str(m['interface']) + "\",\n" +
324 "\"" + str(m['property']) + "\"},\n")
325 params[p] += "}"
Matthew Barth73379f92018-03-15 11:37:10 -0500326 else:
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600327 params[p] = member[p]
328 params['params'] = plist
329 else:
330 params['params'] = []
331 return params
332
333
334def getInit(eGrps, eTrig, events):
335 """
336 Extracts and constructs an init trigger for the event's groups
337 which are required to be of the same type.
338 """
339 method = {}
340 methods = []
Matthew Barth03774012018-10-26 13:25:43 -0500341 if (len(eGrps) > 0):
342 # Use the first group member for retrieving the type
343 member = eGrps[0]['members'][0]
344 if ('method' in eTrig) and \
345 (eTrig['method'] is not None):
346 # Add method parameters
347 eMethod = next(m for m in events['methods']
348 if m['name'] == eTrig['method'])
349 method['method'] = eMethod['name']
350 method['mparams'] = getParameters(
351 member, eGrps, eMethod, events)
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600352
Matthew Barth03774012018-10-26 13:25:43 -0500353 # Add handler parameters
354 eHandler = next(h for h in events['handlers']
355 if h['name'] == eTrig['handler'])
356 method['handler'] = eHandler['name']
357 method['hparams'] = getParameters(
358 member, eGrps, eHandler, events)
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600359
360 methods.append(method)
361
362 return methods
Matthew Barth73379f92018-03-15 11:37:10 -0500363
364
Matthew Bartha69465a2018-03-02 13:50:59 -0600365def getSignal(eGrps, eTrig, events):
366 """
367 Extracts and constructs for each group member a signal
368 subscription of each match listed in the trigger.
369 """
370 signals = []
371 for group in eGrps:
372 for member in group['members']:
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600373 signal = {}
374 # Add signal parameters
375 eSignal = next(s for s in events['signals']
376 if s['name'] == eTrig['signal'])
377 signal['signal'] = eSignal['name']
378 signal['sparams'] = getParameters(member, eGrps, eSignal, events)
379
380 # If service not given, subscribe to signal match
381 if ('service' not in member):
382 # Add signal match parameters
Matthew Bartha69465a2018-03-02 13:50:59 -0600383 eMatch = next(m for m in events['matches']
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600384 if m['name'] == eSignal['match'])
385 signal['match'] = eMatch['name']
386 signal['mparams'] = getParameters(member, eGrps, eMatch, events)
Matthew Bartha69465a2018-03-02 13:50:59 -0600387
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600388 # Add handler parameters
389 eHandler = next(h for h in events['handlers']
390 if h['name'] == eTrig['handler'])
391 signal['handler'] = eHandler['name']
392 signal['hparams'] = getParameters(member, eGrps, eHandler, events)
Matthew Barth73379f92018-03-15 11:37:10 -0500393
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600394 signals.append(signal)
Matthew Barth73379f92018-03-15 11:37:10 -0500395
Matthew Bartha69465a2018-03-02 13:50:59 -0600396 return signals
397
398
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600399def getTimer(eTrig):
400 """
401 Extracts and constructs the required parameters for an
402 event timer.
403 """
404 timer = {}
405 timer['interval'] = (
406 "static_cast<std::chrono::microseconds>" +
407 "(" + str(eTrig['interval']) + ")")
408 timer['type'] = "TimerType::" + str(eTrig['type'])
409 return timer
410
411
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600412def getActions(zNum, zCond, edata, actions, events):
Matthew Barth9df74752017-10-11 14:39:31 -0500413 """
414 Extracts and constructs the make_action function call for
415 all the actions within the given event.
416 """
417 action = []
418 for eActions in actions['actions']:
419 actions = {}
420 eAction = next(a for a in events['actions']
421 if a['name'] == eActions['name'])
422 actions['name'] = eAction['name']
Matthew Barth06fa7812018-11-20 09:54:30 -0600423 actions['groups'] = getGroups(zNum, zCond, eActions, events)
Matthew Barth9df74752017-10-11 14:39:31 -0500424 params = []
425 if ('parameters' in eAction) and \
426 (eAction['parameters'] is not None):
427 for p in eAction['parameters']:
428 param = "static_cast<"
429 if type(eActions[p]) is not dict:
430 if p == 'actions':
431 param = "std::vector<Action>{"
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600432 pActs = getActions(zNum,
433 zCond,
434 edata,
435 eActions,
436 events)
Matthew Barth9df74752017-10-11 14:39:31 -0500437 for a in pActs:
438 if (len(a['parameters']) != 0):
439 param += (
440 "make_action(action::" +
441 a['name'] +
442 "(\n")
443 for i, ap in enumerate(a['parameters']):
444 if (i+1) != len(a['parameters']):
445 param += (ap + ",")
446 else:
447 param += (ap + ")")
448 else:
449 param += ("make_action(action::" + a['name'])
450 param += "),"
451 param += "}"
Matthew Barth799cdf72019-10-08 15:00:10 -0500452 elif p == 'defevents' or p == 'altevents' or p == 'events':
Matthew Bartha1aef7a2019-01-16 11:02:57 -0600453 param = "std::vector<SetSpeedEvent>{\n"
454 for i, e in enumerate(eActions[p]):
455 aEvent = getEvent(zNum, zCond, e, events)
456 if not aEvent:
457 continue
458 if (i+1) != len(eActions[p]):
459 param += genEvent(aEvent) + ",\n"
460 else:
461 param += genEvent(aEvent) + "\n"
462 param += "\t}"
Matthew Barth9df74752017-10-11 14:39:31 -0500463 elif p == 'property':
Matthew Barth9a5b6992018-01-23 15:32:26 -0600464 if isinstance(eActions[p], str) or \
Matthew Barth6c050692017-12-05 15:30:09 -0600465 "string" in str(eActions[p]['type']).lower():
Matthew Barth9a5b6992018-01-23 15:32:26 -0600466 param += (
Matthew Barth6c050692017-12-05 15:30:09 -0600467 str(eActions[p]['type']).lower() +
Matthew Barth9a5b6992018-01-23 15:32:26 -0600468 ">(\"" + str(eActions[p]) + "\")")
469 else:
470 param += (
Matthew Barth6c050692017-12-05 15:30:09 -0600471 str(eActions[p]['type']).lower() +
472 ">(" + str(eActions[p]['value']).lower() + ")")
Matthew Barth9df74752017-10-11 14:39:31 -0500473 else:
474 # Default type to 'size_t' when not given
475 param += ("size_t>(" + str(eActions[p]).lower() + ")")
476 else:
477 if p == 'timer':
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600478 t = getTimer(eActions[p])
Matthew Barth9df74752017-10-11 14:39:31 -0500479 param = (
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600480 "TimerConf{" + t['interval'] + "," +
481 t['type'] + "}")
Matthew Barth9df74752017-10-11 14:39:31 -0500482 else:
483 param += (str(eActions[p]['type']).lower() + ">(")
484 if p != 'map':
Matthew Barth9a5b6992018-01-23 15:32:26 -0600485 if isinstance(eActions[p]['value'], str) or \
486 "string" in str(eActions[p]['type']).lower():
487 param += \
488 "\"" + str(eActions[p]['value']) + "\")"
489 else:
490 param += \
491 str(eActions[p]['value']).lower() + ")"
Matthew Barth9df74752017-10-11 14:39:31 -0500492 else:
493 param += (
494 str(eActions[p]['type']).lower() +
495 convertToMap(str(eActions[p]['value'])) + ")")
496 params.append(param)
497 actions['parameters'] = params
498 action.append(actions)
499 return action
500
501
Matthew Barth7f272fd2017-09-12 16:16:56 -0500502def getEvent(zone_num, zone_conditions, e, events_data):
503 """
504 Parses the sections of an event and populates the properties
505 that construct an event within the generated source.
506 """
507 event = {}
Matthew Barth7f272fd2017-09-12 16:16:56 -0500508
Matthew Barth621a5772018-11-14 14:55:11 -0600509 # Add set speed event name
510 event['name'] = e['name']
511
Matthew Barth6c050692017-12-05 15:30:09 -0600512 # Add set speed event groups
Matthew Barth03774012018-10-26 13:25:43 -0500513 event['groups'] = getGroups(zone_num, zone_conditions, e, events_data)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500514
Matthew Barthe3d1c4a2018-01-11 13:53:49 -0600515 # Add optional set speed actions and function parameters
516 event['action'] = []
517 if ('actions' in e) and \
518 (e['actions'] is not None):
Matthew Barth06fa7812018-11-20 09:54:30 -0600519 # List of dicts containing the list of groups and list of actions
520 sseActions = []
521 eActions = getActions(zone_num, zone_conditions, e, e, events_data)
522 for eAction in eActions:
523 # Skip events that have no groups defined for the event or actions
524 if not event['groups'] and not eAction['groups']:
525 continue
526 # Find group in sseActions
527 grpExists = False
528 for sseDict in sseActions:
529 if eAction['groups'] == sseDict['groups']:
530 # Extend 'actions' list
531 del eAction['groups']
532 sseDict['actions'].append(eAction)
533 grpExists = True
534 break
535 if not grpExists:
536 grps = eAction['groups']
537 del eAction['groups']
538 actList = []
539 actList.append(eAction)
Matthew Bartha6f75162018-11-20 13:50:42 -0600540 sseActions.append({'groups': grps, 'actions': actList})
Matthew Barth06fa7812018-11-20 09:54:30 -0600541 event['action'] = sseActions
Matthew Barth7f272fd2017-09-12 16:16:56 -0500542
Matthew Bartha69465a2018-03-02 13:50:59 -0600543 # Add event triggers
544 event['triggers'] = {}
545 for trig in e['triggers']:
546 triggers = []
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600547 if (trig['name'] == "timer"):
548 event['triggers']['timer'] = getTimer(trig)
549 elif (trig['name'] == "signal"):
Matthew Bartha69465a2018-03-02 13:50:59 -0600550 if ('signals' not in event['triggers']):
551 event['triggers']['signals'] = []
552 triggers = getSignal(event['groups'], trig, events_data)
553 event['triggers']['signals'].extend(triggers)
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600554 elif (trig['name'] == "init"):
555 triggers = getInit(event['groups'], trig, events_data)
556 event['triggers']['init'] = triggers
Matthew Barth7f272fd2017-09-12 16:16:56 -0500557
Matthew Barth7f272fd2017-09-12 16:16:56 -0500558 return event
559
560
561def addPrecondition(zNum, zCond, event, events_data):
Matthew Barth9af190c2017-08-08 14:20:43 -0500562 """
563 Parses the precondition section of an event and populates the necessary
564 structures to generate a precondition for a set speed event.
565 """
566 precond = {}
Matthew Barth621a5772018-11-14 14:55:11 -0600567
568 # Add set speed event precondition name
569 precond['pcname'] = event['name']
570
Matthew Barth9af190c2017-08-08 14:20:43 -0500571 # Add set speed event precondition group
Matthew Barth03774012018-10-26 13:25:43 -0500572 precond['pcgrps'] = getGroups(zNum,
573 zCond,
574 event['precondition'],
575 events_data)
Matthew Barth9af190c2017-08-08 14:20:43 -0500576
Matthew Barth7f272fd2017-09-12 16:16:56 -0500577 # Add set speed event precondition actions
578 pc = []
579 pcs = {}
580 pcs['name'] = event['precondition']['name']
581 epc = next(p for p in events_data['preconditions']
Matthew Barth9af190c2017-08-08 14:20:43 -0500582 if p['name'] == event['precondition']['name'])
583 params = []
Matthew Barth8a697b62018-12-14 13:23:47 -0600584 for p in epc['parameters'] or []:
Matthew Barth9af190c2017-08-08 14:20:43 -0500585 param = {}
586 if p == 'groups':
587 param['type'] = "std::vector<PrecondGroup>"
588 param['open'] = "{"
589 param['close'] = "}"
590 values = []
Matthew Barth6c050692017-12-05 15:30:09 -0600591 for group in precond['pcgrps']:
592 for pcgrp in group['members']:
593 value = {}
594 value['value'] = (
595 "PrecondGroup{\"" +
596 str(pcgrp['object']) + "\",\"" +
597 str(pcgrp['interface']) + "\",\"" +
598 str(pcgrp['property']) + "\"," +
599 "static_cast<" +
600 str(pcgrp['type']).lower() + ">")
601 if isinstance(pcgrp['value'], str) or \
602 "string" in str(pcgrp['type']).lower():
603 value['value'] += ("(" + str(pcgrp['value']) + ")}")
604 else:
605 value['value'] += \
606 ("(" + str(pcgrp['value']).lower() + ")}")
607 values.append(value)
Matthew Barth9af190c2017-08-08 14:20:43 -0500608 param['values'] = values
609 params.append(param)
Matthew Barth7f272fd2017-09-12 16:16:56 -0500610 pcs['params'] = params
611 pc.append(pcs)
Matthew Barth9af190c2017-08-08 14:20:43 -0500612 precond['pcact'] = pc
613
Matthew Barth7f272fd2017-09-12 16:16:56 -0500614 pcevents = []
615 for pce in event['precondition']['events']:
616 pcevent = getEvent(zNum, zCond, pce, events_data)
617 if not pcevent:
618 continue
619 pcevents.append(pcevent)
620 precond['pcevts'] = pcevents
621
Matthew Barthf20c3212018-03-02 14:42:55 -0600622 # Add precondition event triggers
623 precond['triggers'] = {}
624 for trig in event['precondition']['triggers']:
625 triggers = []
Matthew Barthd0b90fc2018-03-05 09:38:45 -0600626 if (trig['name'] == "timer"):
627 precond['triggers']['pctime'] = getTimer(trig)
628 elif (trig['name'] == "signal"):
Matthew Barthf20c3212018-03-02 14:42:55 -0600629 if ('pcsigs' not in precond['triggers']):
630 precond['triggers']['pcsigs'] = []
631 triggers = getSignal(precond['pcgrps'], trig, events_data)
632 precond['triggers']['pcsigs'].extend(triggers)
Matthew Barthcd3bfbc2018-03-07 16:26:03 -0600633 elif (trig['name'] == "init"):
634 triggers = getInit(precond['pcgrps'], trig, events_data)
635 precond['triggers']['init'] = triggers
Matthew Barth9af190c2017-08-08 14:20:43 -0500636
637 return precond
638
639
Gunnar Millsb751f322017-06-06 15:14:11 -0500640def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500641 """
642 Constructs the event entries defined for each zone using the events yaml
643 provided.
644 """
645 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500646
Matthew Barthd4d0f082017-05-16 13:51:10 -0500647 if 'events' in events_data:
648 for e in events_data['events']:
Matthew Barthd4d0f082017-05-16 13:51:10 -0500649 event = {}
Matthew Barth621a5772018-11-14 14:55:11 -0600650
Matthew Barth9af190c2017-08-08 14:20:43 -0500651 # Add precondition if given
652 if ('precondition' in e) and \
653 (e['precondition'] is not None):
Matthew Barth7f272fd2017-09-12 16:16:56 -0500654 event['pc'] = addPrecondition(zone_num,
655 zone_conditions,
656 e,
657 events_data)
Matthew Barth90149802017-08-15 10:51:37 -0500658 else:
Matthew Barth7f272fd2017-09-12 16:16:56 -0500659 event = getEvent(zone_num, zone_conditions, e, events_data)
Matthew Bartha6f75162018-11-20 13:50:42 -0600660 # Remove empty events and events that have
661 # no groups defined for the event or any of the actions
662 if not event or \
663 (not event['groups'] and
664 all(not a['groups'] for a in event['action'])):
Matthew Barth7f272fd2017-09-12 16:16:56 -0500665 continue
Matthew Barthd4d0f082017-05-16 13:51:10 -0500666 events.append(event)
667
668 return events
669
670
Matt Spinler78498c92017-04-11 13:59:46 -0500671def getFansInZone(zone_num, profiles, fan_data):
672 """
673 Parses the fan definition YAML files to find the fans
674 that match both the zone passed in and one of the
675 cooling profiles.
676 """
677
678 fans = []
679
680 for f in fan_data['fans']:
681
682 if zone_num != f['cooling_zone']:
683 continue
684
Gunnar Mills67e95512017-06-02 14:35:18 -0500685 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500686 if f.get('cooling_profile') is None:
687 profile = "all"
688 else:
689 profile = f['cooling_profile']
690
691 if profile not in profiles:
692 continue
693
694 fan = {}
695 fan['name'] = f['inventory']
696 fan['sensors'] = f['sensors']
Lei YU069e4402018-01-31 16:47:37 +0800697 fan['target_interface'] = f.get(
698 'target_interface',
699 'xyz.openbmc_project.Control.FanSpeed')
Chau Ly44872b02022-09-19 07:34:08 +0000700 fan['target_path'] = f.get(
701 'target_path',
702 '/xyz/openbmc_project/sensors/fan_tach/')
Matt Spinler78498c92017-04-11 13:59:46 -0500703 fans.append(fan)
704
705 return fans
706
707
Matthew Barth7883f582019-02-14 14:24:46 -0600708def getIfacesInZone(zone_ifaces):
709 """
710 Parse given interfaces for a zone for associating a zone with an interface
711 and set any properties listed to defined values upon fan control starting
712 on the zone.
713 """
714
715 ifaces = []
716 for i in zone_ifaces:
717 iface = {}
718 # Interface name not needed yet for fan zones but
719 # may be necessary as more interfaces are extended by the zones
720 iface['name'] = i['name']
721
722 if ('properties' in i) and \
723 (i['properties'] is not None):
724 props = []
725 for p in i['properties']:
726 prop = {}
Matthew Barth59096e52019-02-18 12:23:38 -0600727 prop['name'] = p['name']
728 prop['func'] = str(p['name']).lower()
Matthew Barth7883f582019-02-14 14:24:46 -0600729 prop['type'] = parse_cpp_type(p['type'])
Matthew Barth59096e52019-02-18 12:23:38 -0600730 if ('persist' in p):
731 persist = p['persist']
732 if (persist is not None):
733 if (isinstance(persist, bool)):
734 prop['persist'] = 'true' if persist else 'false'
735 else:
736 prop['persist'] = 'false'
Matthew Barth7883f582019-02-14 14:24:46 -0600737 vals = []
738 for v in p['values']:
739 val = v['value']
740 if (val is not None):
741 if (isinstance(val, bool)):
742 # Convert True/False to 'true'/'false'
743 val = 'true' if val else 'false'
744 elif (isinstance(val, str)):
745 # Wrap strings with double-quotes
746 val = "\"" + val + "\""
747 vals.append(val)
748 prop['values'] = vals
749 props.append(prop)
750 iface['props'] = props
751 ifaces.append(iface)
752
753 return ifaces
754
755
Gunnar Millsee8a2812017-06-02 14:26:47 -0500756def getConditionInZoneConditions(zone_condition, zone_conditions_data):
757 """
758 Parses the zone conditions definition YAML files to find the condition
759 that match both the zone condition passed in.
760 """
761
762 condition = {}
763
764 for c in zone_conditions_data['conditions']:
765
766 if zone_condition != c['name']:
767 continue
768 condition['type'] = c['type']
769 properties = []
770 for p in c['properties']:
771 property = {}
772 property['property'] = p['property']
773 property['interface'] = p['interface']
774 property['path'] = p['path']
775 property['type'] = p['type'].lower()
776 property['value'] = str(p['value']).lower()
777 properties.append(property)
778 condition['properties'] = properties
779
780 return condition
781
782
783def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
Matt Spinler78498c92017-04-11 13:59:46 -0500784 """
785 Combines the zone definition YAML and fan
786 definition YAML to create a data structure defining
787 the fan cooling zones.
788 """
789
790 zone_groups = []
791
Matthew Barth005ff2f2020-02-18 11:17:41 -0600792 # Allow zone_conditions to not be in yaml (since its optional)
793 if not isinstance(zone_data, list) and zone_data != {}:
794 zone_data = [zone_data]
Matt Spinler78498c92017-04-11 13:59:46 -0500795 for group in zone_data:
796 conditions = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500797 # zone conditions are optional
798 if 'zone_conditions' in group and group['zone_conditions'] is not None:
799 for c in group['zone_conditions']:
800
801 if not zone_conditions_data:
Gunnar Millsb751f322017-06-06 15:14:11 -0500802 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500803 "zone_conditions used in zone YAML")
804
805 condition = getConditionInZoneConditions(c['name'],
806 zone_conditions_data)
807
808 if not condition:
809 sys.exit("Missing zone condition " + c['name'])
810
811 conditions.append(condition)
Matt Spinler78498c92017-04-11 13:59:46 -0500812
813 zone_group = {}
814 zone_group['conditions'] = conditions
815
816 zones = []
817 for z in group['zones']:
818 zone = {}
819
Gunnar Mills67e95512017-06-02 14:35:18 -0500820 # 'zone' is required
821 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500822 sys.exit("Missing fan zone number in " + zone_yaml)
823
824 zone['num'] = z['zone']
825
826 zone['full_speed'] = z['full_speed']
827
Matthew Barth1de66622017-06-12 13:13:02 -0500828 zone['default_floor'] = z['default_floor']
829
Matthew Bartha9561842017-06-29 11:43:45 -0500830 # 'increase_delay' is optional (use 0 by default)
831 key = 'increase_delay'
832 zone[key] = z.setdefault(key, 0)
833
834 # 'decrease_interval' is optional (use 0 by default)
835 key = 'decrease_interval'
836 zone[key] = z.setdefault(key, 0)
837
Gunnar Mills67e95512017-06-02 14:35:18 -0500838 # 'cooling_profiles' is optional (use 'all' instead)
839 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500840 (z['cooling_profiles'] is None):
841 profiles = ["all"]
842 else:
843 profiles = z['cooling_profiles']
844
Matthew Barth7883f582019-02-14 14:24:46 -0600845 # 'interfaces' is optional (no default)
Matthew Barth64099cd2019-02-18 09:43:12 -0600846 ifaces = []
Matthew Barth7883f582019-02-14 14:24:46 -0600847 if ('interfaces' in z) and \
848 (z['interfaces'] is not None):
849 ifaces = getIfacesInZone(z['interfaces'])
850
Matt Spinler78498c92017-04-11 13:59:46 -0500851 fans = getFansInZone(z['zone'], profiles, fan_data)
Matthew Barth005ff2f2020-02-18 11:17:41 -0600852 events = getEventsInZone(z['zone'],
853 group.get('zone_conditions', {}),
Gunnar Millsb751f322017-06-06 15:14:11 -0500854 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500855
856 if len(fans) == 0:
857 sys.exit("Didn't find any fans in zone " + str(zone['num']))
858
Matthew Barth7883f582019-02-14 14:24:46 -0600859 if (ifaces):
860 zone['ifaces'] = ifaces
Matt Spinler78498c92017-04-11 13:59:46 -0500861 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500862 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500863 zones.append(zone)
864
865 zone_group['zones'] = zones
866 zone_groups.append(zone_group)
867
868 return zone_groups
869
870
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500871if __name__ == '__main__':
872 parser = ArgumentParser(
873 description="Phosphor fan zone definition parser")
874
875 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
876 default="example/zones.yaml",
877 help='fan zone definitional yaml')
878 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
879 default="example/fans.yaml",
880 help='fan definitional yaml')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500881 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
882 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500883 parser.add_argument('-c', '--zone_conditions_yaml',
884 dest='zone_conditions_yaml',
885 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500886 parser.add_argument('-o', '--output_dir', dest='output_dir',
887 default=".",
888 help='output directory')
889 args = parser.parse_args()
890
891 if not args.zone_yaml or not args.fan_yaml:
892 parser.print_usage()
William A. Kennington III3e781062018-10-19 17:18:34 -0700893 sys.exit(1)
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500894
895 with open(args.zone_yaml, 'r') as zone_input:
896 zone_data = yaml.safe_load(zone_input) or {}
897
898 with open(args.fan_yaml, 'r') as fan_input:
899 fan_data = yaml.safe_load(fan_input) or {}
900
Matthew Barthd4d0f082017-05-16 13:51:10 -0500901 events_data = {}
902 if args.events_yaml:
903 with open(args.events_yaml, 'r') as events_input:
904 events_data = yaml.safe_load(events_input) or {}
905
Gunnar Millsee8a2812017-06-02 14:26:47 -0500906 zone_conditions_data = {}
907 if args.zone_conditions_yaml:
908 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
909 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
910
Matt Spinleree7f6422017-05-09 11:03:14 -0500911 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500912 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500913
914 manager_config = zone_data.get('manager_configuration', {})
915
916 if manager_config.get('power_on_delay') is None:
917 manager_config['power_on_delay'] = 0
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500918
Matthew Barth702c4a52018-02-28 16:23:11 -0600919 tmpls_dir = os.path.join(
920 os.path.dirname(os.path.realpath(__file__)),
921 "templates")
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500922 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
Matthew Barth702c4a52018-02-28 16:23:11 -0600923 if sys.version_info < (3, 0):
924 lkup = TemplateLookup(
925 directories=tmpls_dir.split(),
926 disable_unicode=True)
927 else:
928 lkup = TemplateLookup(
929 directories=tmpls_dir.split())
930 tmpl = lkup.get_template('fan_zone_defs.mako.cpp')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500931 with open(output_file, 'w') as output:
Matthew Barth702c4a52018-02-28 16:23:11 -0600932 output.write(tmpl.render(zones=zone_config,
933 mgr_data=manager_config))