1#!/usr/bin/env python3 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 13from mako.lookup import TemplateLookup 14 15 16def 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([get_cpp_type(e) for e in rest]) 118 result += '>' 119 120 return result 121 122 123def convertToMap(listOfDict): 124 """ 125 Converts a list of dictionary entries to a std::map initialization list. 126 """ 127 listOfDict = listOfDict.replace('\'', '\"') 128 listOfDict = listOfDict.replace('[', '{') 129 listOfDict = listOfDict.replace(']', '}') 130 listOfDict = listOfDict.replace(':', ',') 131 return listOfDict 132 133 134def genEvent(event): 135 """ 136 Generates the source code of an event and returns it as a string 137 """ 138 e = "SetSpeedEvent{\n" 139 e += "\"" + event['name'] + "\",\n" 140 e += "Group{\n" 141 for group in event['groups']: 142 for member in group['members']: 143 e += "{\"" + member['object'] + "\",\n" 144 e += "\"" + member['interface'] + "\",\n" 145 e += "\"" + member['property'] + "\"},\n" 146 e += "},\n" 147 148 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']: 153 e += "{\"" + m['object'] + "\",\n" 154 e += "\"" + m['interface'] + "\",\n" 155 e += "\"" + m['property'] + "\"},\n" 156 e += "},\n" 157 e += "std::vector<Action>{\n" 158 for a in d['actions']: 159 if len(a['parameters']) != 0: 160 e += "make_action(action::" + a['name'] + "(\n" 161 else: 162 e += "make_action(action::" + a['name'] + "\n" 163 for i, p in enumerate(a['parameters']): 164 if (i+1) != len(a['parameters']): 165 e += p + ",\n" 166 else: 167 e += p + "\n" 168 if len(a['parameters']) != 0: 169 e += ")),\n" 170 else: 171 e += "),\n" 172 e += "}},\n" 173 e += "},\n" 174 175 e += "std::vector<Trigger>{\n" 176 if ('timer' in event['triggers']) and \ 177 (event['triggers']['timer'] is not None): 178 e += "\tmake_trigger(trigger::timer(TimerConf{\n" 179 e += "\t" + event['triggers']['timer']['interval'] + ",\n" 180 e += "\t" + event['triggers']['timer']['type'] + "\n" 181 e += "\t})),\n" 182 183 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" 188 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" 191 else: 192 e += "\t\t\t" + s['mparams'][mp] + "\n" 193 e += "\t\t),\n" 194 e += "\t\tmake_handler<SignalHandler>(\n" 195 if ('type' in s['sparams']) and (s['sparams']['type'] is not None): 196 e += s['signal'] + "<" + s['sparams']['type'] + ">(\n" 197 else: 198 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") 204 else: 205 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" 212 e += "\t\t)\n" 213 e += "\t)),\n" 214 215 if ('init' in event['triggers']): 216 for i in event['triggers']['init']: 217 e += "\tmake_trigger(trigger::init(\n" 218 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" 227 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" 238 e += "))\n" 239 e += "\t\t)\n" 240 e += "\t)),\n" 241 242 e += "},\n" 243 244 e += "}" 245 246 return e 247 248 249def getGroups(zNum, zCond, edata, events): 250 """ 251 Extract and construct the groups for the given event. 252 """ 253 groups = [] 254 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 264 265 # 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']) 273 274 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) 300 return groups 301 302 303def getParameters(member, groups, section, events): 304 """ 305 Extracts and constructs a section's parameters 306 """ 307 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] + "\"" 317 else: 318 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] += "}" 326 else: 327 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 = [] 341 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) 352 353 # 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) 359 360 methods.append(method) 361 362 return methods 363 364 365def 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']: 373 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 383 eMatch = next(m for m in events['matches'] 384 if m['name'] == eSignal['match']) 385 signal['match'] = eMatch['name'] 386 signal['mparams'] = getParameters(member, eGrps, eMatch, events) 387 388 # 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) 393 394 signals.append(signal) 395 396 return signals 397 398 399def 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 412def getActions(zNum, zCond, edata, actions, events): 413 """ 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'] 423 actions['groups'] = getGroups(zNum, zCond, eActions, events) 424 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>{" 432 pActs = getActions(zNum, 433 zCond, 434 edata, 435 eActions, 436 events) 437 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 += "}" 452 elif p == 'defevents' or p == 'altevents' or p == 'events': 453 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}" 463 elif p == 'property': 464 if isinstance(eActions[p], str) or \ 465 "string" in str(eActions[p]['type']).lower(): 466 param += ( 467 str(eActions[p]['type']).lower() + 468 ">(\"" + str(eActions[p]) + "\")") 469 else: 470 param += ( 471 str(eActions[p]['type']).lower() + 472 ">(" + str(eActions[p]['value']).lower() + ")") 473 else: 474 # Default type to 'size_t' when not given 475 param += ("size_t>(" + str(eActions[p]).lower() + ")") 476 else: 477 if p == 'timer': 478 t = getTimer(eActions[p]) 479 param = ( 480 "TimerConf{" + t['interval'] + "," + 481 t['type'] + "}") 482 else: 483 param += (str(eActions[p]['type']).lower() + ">(") 484 if p != 'map': 485 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() + ")" 492 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 502def 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 = {} 508 509 # Add set speed event name 510 event['name'] = e['name'] 511 512 # Add set speed event groups 513 event['groups'] = getGroups(zone_num, zone_conditions, e, events_data) 514 515 # Add optional set speed actions and function parameters 516 event['action'] = [] 517 if ('actions' in e) and \ 518 (e['actions'] is not None): 519 # 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) 540 sseActions.append({'groups': grps, 'actions': actList}) 541 event['action'] = sseActions 542 543 # Add event triggers 544 event['triggers'] = {} 545 for trig in e['triggers']: 546 triggers = [] 547 if (trig['name'] == "timer"): 548 event['triggers']['timer'] = getTimer(trig) 549 elif (trig['name'] == "signal"): 550 if ('signals' not in event['triggers']): 551 event['triggers']['signals'] = [] 552 triggers = getSignal(event['groups'], trig, events_data) 553 event['triggers']['signals'].extend(triggers) 554 elif (trig['name'] == "init"): 555 triggers = getInit(event['groups'], trig, events_data) 556 event['triggers']['init'] = triggers 557 558 return event 559 560 561def addPrecondition(zNum, zCond, event, events_data): 562 """ 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 = {} 567 568 # Add set speed event precondition name 569 precond['pcname'] = event['name'] 570 571 # Add set speed event precondition group 572 precond['pcgrps'] = getGroups(zNum, 573 zCond, 574 event['precondition'], 575 events_data) 576 577 # 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'] 582 if p['name'] == event['precondition']['name']) 583 params = [] 584 for p in epc['parameters'] or []: 585 param = {} 586 if p == 'groups': 587 param['type'] = "std::vector<PrecondGroup>" 588 param['open'] = "{" 589 param['close'] = "}" 590 values = [] 591 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) 608 param['values'] = values 609 params.append(param) 610 pcs['params'] = params 611 pc.append(pcs) 612 precond['pcact'] = pc 613 614 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 622 # Add precondition event triggers 623 precond['triggers'] = {} 624 for trig in event['precondition']['triggers']: 625 triggers = [] 626 if (trig['name'] == "timer"): 627 precond['triggers']['pctime'] = getTimer(trig) 628 elif (trig['name'] == "signal"): 629 if ('pcsigs' not in precond['triggers']): 630 precond['triggers']['pcsigs'] = [] 631 triggers = getSignal(precond['pcgrps'], trig, events_data) 632 precond['triggers']['pcsigs'].extend(triggers) 633 elif (trig['name'] == "init"): 634 triggers = getInit(precond['pcgrps'], trig, events_data) 635 precond['triggers']['init'] = triggers 636 637 return precond 638 639 640def getEventsInZone(zone_num, zone_conditions, events_data): 641 """ 642 Constructs the event entries defined for each zone using the events yaml 643 provided. 644 """ 645 events = [] 646 647 if 'events' in events_data: 648 for e in events_data['events']: 649 event = {} 650 651 # Add precondition if given 652 if ('precondition' in e) and \ 653 (e['precondition'] is not None): 654 event['pc'] = addPrecondition(zone_num, 655 zone_conditions, 656 e, 657 events_data) 658 else: 659 event = getEvent(zone_num, zone_conditions, e, events_data) 660 # 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'])): 665 continue 666 events.append(event) 667 668 return events 669 670 671def 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 685 # 'cooling_profile' is optional (use 'all' instead) 686 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'] 697 fan['target_interface'] = f.get( 698 'target_interface', 699 'xyz.openbmc_project.Control.FanSpeed') 700 fan['target_path'] = f.get( 701 'target_path', 702 '/xyz/openbmc_project/sensors/fan_tach/') 703 fans.append(fan) 704 705 return fans 706 707 708def 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 = {} 727 prop['name'] = p['name'] 728 prop['func'] = str(p['name']).lower() 729 prop['type'] = parse_cpp_type(p['type']) 730 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' 737 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 756def 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): 784 """ 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 792 # 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] 795 for group in zone_data: 796 conditions = [] 797 # 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: 802 sys.exit("No zone_conditions YAML file but " + 803 "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) 812 813 zone_group = {} 814 zone_group['conditions'] = conditions 815 816 zones = [] 817 for z in group['zones']: 818 zone = {} 819 820 # 'zone' is required 821 if ('zone' not in z) or (z['zone'] is None): 822 sys.exit("Missing fan zone number in " + zone_yaml) 823 824 zone['num'] = z['zone'] 825 826 zone['full_speed'] = z['full_speed'] 827 828 zone['default_floor'] = z['default_floor'] 829 830 # '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 838 # 'cooling_profiles' is optional (use 'all' instead) 839 if ('cooling_profiles' not in z) or \ 840 (z['cooling_profiles'] is None): 841 profiles = ["all"] 842 else: 843 profiles = z['cooling_profiles'] 844 845 # 'interfaces' is optional (no default) 846 ifaces = [] 847 if ('interfaces' in z) and \ 848 (z['interfaces'] is not None): 849 ifaces = getIfacesInZone(z['interfaces']) 850 851 fans = getFansInZone(z['zone'], profiles, fan_data) 852 events = getEventsInZone(z['zone'], 853 group.get('zone_conditions', {}), 854 events_data) 855 856 if len(fans) == 0: 857 sys.exit("Didn't find any fans in zone " + str(zone['num'])) 858 859 if (ifaces): 860 zone['ifaces'] = ifaces 861 zone['fans'] = fans 862 zone['events'] = events 863 zones.append(zone) 864 865 zone_group['zones'] = zones 866 zone_groups.append(zone_group) 867 868 return zone_groups 869 870 871if __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') 881 parser.add_argument('-e', '--events_yaml', dest='events_yaml', 882 help='events to set speeds yaml') 883 parser.add_argument('-c', '--zone_conditions_yaml', 884 dest='zone_conditions_yaml', 885 help='conditions to determine zone yaml') 886 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() 893 sys.exit(1) 894 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 901 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 906 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 911 zone_config = buildZoneData(zone_data.get('zone_configuration', {}), 912 fan_data, events_data, zone_conditions_data) 913 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 918 919 tmpls_dir = os.path.join( 920 os.path.dirname(os.path.realpath(__file__)), 921 "templates") 922 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp") 923 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') 931 with open(output_file, 'w') as output: 932 output.write(tmpl.render(zones=zone_config, 933 mgr_data=manager_config)) 934