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 fans.append(fan) 701 702 return fans 703 704 705def getIfacesInZone(zone_ifaces): 706 """ 707 Parse given interfaces for a zone for associating a zone with an interface 708 and set any properties listed to defined values upon fan control starting 709 on the zone. 710 """ 711 712 ifaces = [] 713 for i in zone_ifaces: 714 iface = {} 715 # Interface name not needed yet for fan zones but 716 # may be necessary as more interfaces are extended by the zones 717 iface['name'] = i['name'] 718 719 if ('properties' in i) and \ 720 (i['properties'] is not None): 721 props = [] 722 for p in i['properties']: 723 prop = {} 724 prop['name'] = p['name'] 725 prop['func'] = str(p['name']).lower() 726 prop['type'] = parse_cpp_type(p['type']) 727 if ('persist' in p): 728 persist = p['persist'] 729 if (persist is not None): 730 if (isinstance(persist, bool)): 731 prop['persist'] = 'true' if persist else 'false' 732 else: 733 prop['persist'] = 'false' 734 vals = [] 735 for v in p['values']: 736 val = v['value'] 737 if (val is not None): 738 if (isinstance(val, bool)): 739 # Convert True/False to 'true'/'false' 740 val = 'true' if val else 'false' 741 elif (isinstance(val, str)): 742 # Wrap strings with double-quotes 743 val = "\"" + val + "\"" 744 vals.append(val) 745 prop['values'] = vals 746 props.append(prop) 747 iface['props'] = props 748 ifaces.append(iface) 749 750 return ifaces 751 752 753def getConditionInZoneConditions(zone_condition, zone_conditions_data): 754 """ 755 Parses the zone conditions definition YAML files to find the condition 756 that match both the zone condition passed in. 757 """ 758 759 condition = {} 760 761 for c in zone_conditions_data['conditions']: 762 763 if zone_condition != c['name']: 764 continue 765 condition['type'] = c['type'] 766 properties = [] 767 for p in c['properties']: 768 property = {} 769 property['property'] = p['property'] 770 property['interface'] = p['interface'] 771 property['path'] = p['path'] 772 property['type'] = p['type'].lower() 773 property['value'] = str(p['value']).lower() 774 properties.append(property) 775 condition['properties'] = properties 776 777 return condition 778 779 780def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data): 781 """ 782 Combines the zone definition YAML and fan 783 definition YAML to create a data structure defining 784 the fan cooling zones. 785 """ 786 787 zone_groups = [] 788 789 # Allow zone_conditions to not be in yaml (since its optional) 790 if not isinstance(zone_data, list) and zone_data != {}: 791 zone_data = [zone_data] 792 for group in zone_data: 793 conditions = [] 794 # zone conditions are optional 795 if 'zone_conditions' in group and group['zone_conditions'] is not None: 796 for c in group['zone_conditions']: 797 798 if not zone_conditions_data: 799 sys.exit("No zone_conditions YAML file but " + 800 "zone_conditions used in zone YAML") 801 802 condition = getConditionInZoneConditions(c['name'], 803 zone_conditions_data) 804 805 if not condition: 806 sys.exit("Missing zone condition " + c['name']) 807 808 conditions.append(condition) 809 810 zone_group = {} 811 zone_group['conditions'] = conditions 812 813 zones = [] 814 for z in group['zones']: 815 zone = {} 816 817 # 'zone' is required 818 if ('zone' not in z) or (z['zone'] is None): 819 sys.exit("Missing fan zone number in " + zone_yaml) 820 821 zone['num'] = z['zone'] 822 823 zone['full_speed'] = z['full_speed'] 824 825 zone['default_floor'] = z['default_floor'] 826 827 # 'increase_delay' is optional (use 0 by default) 828 key = 'increase_delay' 829 zone[key] = z.setdefault(key, 0) 830 831 # 'decrease_interval' is optional (use 0 by default) 832 key = 'decrease_interval' 833 zone[key] = z.setdefault(key, 0) 834 835 # 'cooling_profiles' is optional (use 'all' instead) 836 if ('cooling_profiles' not in z) or \ 837 (z['cooling_profiles'] is None): 838 profiles = ["all"] 839 else: 840 profiles = z['cooling_profiles'] 841 842 # 'interfaces' is optional (no default) 843 ifaces = [] 844 if ('interfaces' in z) and \ 845 (z['interfaces'] is not None): 846 ifaces = getIfacesInZone(z['interfaces']) 847 848 fans = getFansInZone(z['zone'], profiles, fan_data) 849 events = getEventsInZone(z['zone'], 850 group.get('zone_conditions', {}), 851 events_data) 852 853 if len(fans) == 0: 854 sys.exit("Didn't find any fans in zone " + str(zone['num'])) 855 856 if (ifaces): 857 zone['ifaces'] = ifaces 858 zone['fans'] = fans 859 zone['events'] = events 860 zones.append(zone) 861 862 zone_group['zones'] = zones 863 zone_groups.append(zone_group) 864 865 return zone_groups 866 867 868if __name__ == '__main__': 869 parser = ArgumentParser( 870 description="Phosphor fan zone definition parser") 871 872 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml', 873 default="example/zones.yaml", 874 help='fan zone definitional yaml') 875 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml', 876 default="example/fans.yaml", 877 help='fan definitional yaml') 878 parser.add_argument('-e', '--events_yaml', dest='events_yaml', 879 help='events to set speeds yaml') 880 parser.add_argument('-c', '--zone_conditions_yaml', 881 dest='zone_conditions_yaml', 882 help='conditions to determine zone yaml') 883 parser.add_argument('-o', '--output_dir', dest='output_dir', 884 default=".", 885 help='output directory') 886 args = parser.parse_args() 887 888 if not args.zone_yaml or not args.fan_yaml: 889 parser.print_usage() 890 sys.exit(1) 891 892 with open(args.zone_yaml, 'r') as zone_input: 893 zone_data = yaml.safe_load(zone_input) or {} 894 895 with open(args.fan_yaml, 'r') as fan_input: 896 fan_data = yaml.safe_load(fan_input) or {} 897 898 events_data = {} 899 if args.events_yaml: 900 with open(args.events_yaml, 'r') as events_input: 901 events_data = yaml.safe_load(events_input) or {} 902 903 zone_conditions_data = {} 904 if args.zone_conditions_yaml: 905 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input: 906 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {} 907 908 zone_config = buildZoneData(zone_data.get('zone_configuration', {}), 909 fan_data, events_data, zone_conditions_data) 910 911 manager_config = zone_data.get('manager_configuration', {}) 912 913 if manager_config.get('power_on_delay') is None: 914 manager_config['power_on_delay'] = 0 915 916 tmpls_dir = os.path.join( 917 os.path.dirname(os.path.realpath(__file__)), 918 "templates") 919 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp") 920 if sys.version_info < (3, 0): 921 lkup = TemplateLookup( 922 directories=tmpls_dir.split(), 923 disable_unicode=True) 924 else: 925 lkup = TemplateLookup( 926 directories=tmpls_dir.split()) 927 tmpl = lkup.get_template('fan_zone_defs.mako.cpp') 928 with open(output_file, 'w') as output: 929 output.write(tmpl.render(zones=zone_config, 930 mgr_data=manager_config)) 931