1#!/usr/bin/env python 2 3""" 4This script reads in fan definition and zone definition YAML 5files and generates a set of structures for use by the fan control code. 6""" 7 8import os 9import sys 10import yaml 11from argparse import ArgumentParser 12from mako.template import Template 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(map(lambda e: get_cpp_type(e), 118 rest)) 119 result += '>' 120 121 return result 122 123 124def convertToMap(listOfDict): 125 """ 126 Converts a list of dictionary entries to a std::map initialization list. 127 """ 128 listOfDict = listOfDict.replace('\'', '\"') 129 listOfDict = listOfDict.replace('[', '{') 130 listOfDict = listOfDict.replace(']', '}') 131 listOfDict = listOfDict.replace(':', ',') 132 return listOfDict 133 134 135def genEvent(event): 136 """ 137 Generates the source code of an event and returns it as a string 138 """ 139 e = "SetSpeedEvent{\n" 140 e += "\"" + event['name'] + "\",\n" 141 e += "Group{\n" 142 for group in event['groups']: 143 for member in group['members']: 144 e += "{\"" + member['object'] + "\",\n" 145 e += "\"" + member['interface'] + "\",\n" 146 e += "\"" + member['property'] + "\"},\n" 147 e += "},\n" 148 149 e += "ActionData{\n" 150 for d in event['action']: 151 e += "{Group{\n" 152 for g in d['groups']: 153 for m in g['members']: 154 e += "{" + m['object'] + ",\n" 155 e += m['interface'] + ",\n" 156 e += m['property'] + "},\n" 157 e += "},\n" 158 e += "std::vector<Action>{\n" 159 for a in d['actions']: 160 e += "make_action(action::" + a['name'] + "(\n" 161 for i, p in enumerate(a['parameters']): 162 if (i+1) != len(a['parameters']): 163 e += p + ",\n" 164 else: 165 e += p + "\n" 166 e += ")),\n" 167 e += "}},\n" 168 e += "},\n" 169 170 e += "std::vector<Trigger>{\n" 171 if ('timer' in event['triggers']) and \ 172 (event['triggers']['timer'] is not None): 173 e += "\tmake_trigger(trigger::timer(TimerConf{\n" 174 e += "\t" + event['triggers']['timer']['interval'] + ",\n" 175 e += "\t" + event['triggers']['timer']['type'] + "\n" 176 e += "\t})),\n" 177 178 if ('signals' in event['triggers']) and \ 179 (event['triggers']['signals'] is not None): 180 for s in event['triggers']['signals']: 181 e += "\tmake_trigger(trigger::signal(\n" 182 e += "match::" + s['match'] + "(\n" 183 for i, mp in enumerate(s['mparams']['params']): 184 if (i+1) != len(s['mparams']['params']): 185 e += "\t\t\t" + s['mparams'][mp] + ",\n" 186 else: 187 e += "\t\t\t" + s['mparams'][mp] + "\n" 188 e += "\t\t),\n" 189 e += "\t\tmake_handler<SignalHandler>(\n" 190 if ('type' in s['sparams']) and (s['sparams']['type'] is not None): 191 e += s['signal'] + "<" + s['sparams']['type'] + ">(\n" 192 else: 193 e += s['signal'] + "(\n" 194 for sp in s['sparams']['params']: 195 e += s['sparams'][sp] + ",\n" 196 if ('type' in s['hparams']) and (s['hparams']['type'] is not None): 197 e += ("handler::" + s['handler'] + 198 "<" + s['hparams']['type'] + ">(\n") 199 else: 200 e += "handler::" + s['handler'] + "(\n)" 201 for i, hp in enumerate(s['hparams']['params']): 202 if (i+1) != len(s['hparams']['params']): 203 e += s['hparams'][hp] + ",\n" 204 else: 205 e += s['hparams'][hp] + "\n" 206 e += "))\n" 207 e += "\t\t)\n" 208 e += "\t)),\n" 209 210 if ('init' in event['triggers']): 211 for i in event['triggers']['init']: 212 e += "\tmake_trigger(trigger::init(\n" 213 if ('method' in i): 214 e += "\t\tmake_handler<MethodHandler>(\n" 215 if ('type' in i['mparams']) and \ 216 (i['mparams']['type'] is not None): 217 e += i['method'] + "<" + i['mparams']['type'] + ">(\n" 218 else: 219 e += i['method'] + "(\n" 220 for ip in i['mparams']['params']: 221 e += i['mparams'][ip] + ",\n" 222 if ('type' in i['hparams']) and \ 223 (i['hparams']['type'] is not None): 224 e += ("handler::" + i['handler'] + 225 "<" + i['hparams']['type'] + ">(\n") 226 else: 227 e += "handler::" + i['handler'] + "(\n)" 228 for i, hp in enumerate(i['hparams']['params']): 229 if (i+1) != len(i['hparams']['params']): 230 e += i['hparams'][hp] + ",\n" 231 else: 232 e += i['hparams'][hp] + "\n" 233 e += "))\n" 234 e += "\t\t)\n" 235 e += "\t)),\n" 236 237 e += "},\n" 238 239 e += "}" 240 241 return e 242 243 244def getGroups(zNum, zCond, edata, events): 245 """ 246 Extract and construct the groups for the given event. 247 """ 248 groups = [] 249 if ('groups' in edata) and (edata['groups'] is not None): 250 for eGroups in edata['groups']: 251 if ('zone_conditions' in eGroups) and \ 252 (eGroups['zone_conditions'] is not None): 253 # Zone conditions are optional in the events yaml but skip 254 # if this event's condition is not in this zone's conditions 255 if all('name' in z and z['name'] is not None and 256 not any(c['name'] == z['name'] for c in zCond) 257 for z in eGroups['zone_conditions']): 258 continue 259 260 # Zone numbers are optional in the events yaml but skip if this 261 # zone's zone number is not in the event's zone numbers 262 if all('zones' in z and z['zones'] is not None and 263 zNum not in z['zones'] 264 for z in eGroups['zone_conditions']): 265 continue 266 eGroup = next(g for g in events['groups'] 267 if g['name'] == eGroups['name']) 268 269 group = {} 270 members = [] 271 group['name'] = eGroup['name'] 272 for m in eGroup['members']: 273 member = {} 274 member['path'] = eGroup['type'] 275 member['object'] = (eGroup['type'] + m) 276 member['interface'] = eGroups['interface'] 277 member['property'] = eGroups['property']['name'] 278 member['type'] = eGroups['property']['type'] 279 # Use defined service to note member on zone object 280 if ('service' in eGroup) and \ 281 (eGroup['service'] is not None): 282 member['service'] = eGroup['service'] 283 # Add expected group member's property value if given 284 if ('value' in eGroups['property']) and \ 285 (eGroups['property']['value'] is not None): 286 if isinstance(eGroups['property']['value'], str) or \ 287 "string" in str(member['type']).lower(): 288 member['value'] = ( 289 "\"" + eGroups['property']['value'] + "\"") 290 else: 291 member['value'] = eGroups['property']['value'] 292 members.append(member) 293 group['members'] = members 294 groups.append(group) 295 return groups 296 297 298def getParameters(member, groups, section, events): 299 """ 300 Extracts and constructs a section's parameters 301 """ 302 params = {} 303 if ('parameters' in section) and \ 304 (section['parameters'] is not None): 305 plist = [] 306 for sp in section['parameters']: 307 p = str(sp) 308 if (p != 'type'): 309 plist.append(p) 310 if (p != 'group'): 311 params[p] = "\"" + member[p] + "\"" 312 else: 313 params[p] = "Group\n{\n" 314 for g in groups: 315 for m in g['members']: 316 params[p] += ( 317 "{\"" + str(m['object']) + "\",\n" + 318 "\"" + str(m['interface']) + "\",\n" + 319 "\"" + str(m['property']) + "\"},\n") 320 params[p] += "}" 321 else: 322 params[p] = member[p] 323 params['params'] = plist 324 else: 325 params['params'] = [] 326 return params 327 328 329def getInit(eGrps, eTrig, events): 330 """ 331 Extracts and constructs an init trigger for the event's groups 332 which are required to be of the same type. 333 """ 334 method = {} 335 methods = [] 336 if (len(eGrps) > 0): 337 # Use the first group member for retrieving the type 338 member = eGrps[0]['members'][0] 339 if ('method' in eTrig) and \ 340 (eTrig['method'] is not None): 341 # Add method parameters 342 eMethod = next(m for m in events['methods'] 343 if m['name'] == eTrig['method']) 344 method['method'] = eMethod['name'] 345 method['mparams'] = getParameters( 346 member, eGrps, eMethod, events) 347 348 # Add handler parameters 349 eHandler = next(h for h in events['handlers'] 350 if h['name'] == eTrig['handler']) 351 method['handler'] = eHandler['name'] 352 method['hparams'] = getParameters( 353 member, eGrps, eHandler, events) 354 355 methods.append(method) 356 357 return methods 358 359 360def getSignal(eGrps, eTrig, events): 361 """ 362 Extracts and constructs for each group member a signal 363 subscription of each match listed in the trigger. 364 """ 365 signals = [] 366 for group in eGrps: 367 for member in group['members']: 368 signal = {} 369 # Add signal parameters 370 eSignal = next(s for s in events['signals'] 371 if s['name'] == eTrig['signal']) 372 signal['signal'] = eSignal['name'] 373 signal['sparams'] = getParameters(member, eGrps, eSignal, events) 374 375 # If service not given, subscribe to signal match 376 if ('service' not in member): 377 # Add signal match parameters 378 eMatch = next(m for m in events['matches'] 379 if m['name'] == eSignal['match']) 380 signal['match'] = eMatch['name'] 381 signal['mparams'] = getParameters(member, eGrps, eMatch, events) 382 383 # Add handler parameters 384 eHandler = next(h for h in events['handlers'] 385 if h['name'] == eTrig['handler']) 386 signal['handler'] = eHandler['name'] 387 signal['hparams'] = getParameters(member, eGrps, eHandler, events) 388 389 signals.append(signal) 390 391 return signals 392 393 394def getTimer(eTrig): 395 """ 396 Extracts and constructs the required parameters for an 397 event timer. 398 """ 399 timer = {} 400 timer['interval'] = ( 401 "static_cast<std::chrono::microseconds>" + 402 "(" + str(eTrig['interval']) + ")") 403 timer['type'] = "TimerType::" + str(eTrig['type']) 404 return timer 405 406 407def getActions(zNum, zCond, edata, actions, events): 408 """ 409 Extracts and constructs the make_action function call for 410 all the actions within the given event. 411 """ 412 action = [] 413 for eActions in actions['actions']: 414 actions = {} 415 eAction = next(a for a in events['actions'] 416 if a['name'] == eActions['name']) 417 actions['name'] = eAction['name'] 418 actions['groups'] = getGroups(zNum, zCond, eActions, events) 419 params = [] 420 if ('parameters' in eAction) and \ 421 (eAction['parameters'] is not None): 422 for p in eAction['parameters']: 423 param = "static_cast<" 424 if type(eActions[p]) is not dict: 425 if p == 'actions': 426 param = "std::vector<Action>{" 427 pActs = getActions(zNum, 428 zCond, 429 edata, 430 eActions, 431 events) 432 for a in pActs: 433 if (len(a['parameters']) != 0): 434 param += ( 435 "make_action(action::" + 436 a['name'] + 437 "(\n") 438 for i, ap in enumerate(a['parameters']): 439 if (i+1) != len(a['parameters']): 440 param += (ap + ",") 441 else: 442 param += (ap + ")") 443 else: 444 param += ("make_action(action::" + a['name']) 445 param += ")," 446 param += "}" 447 elif p == 'defevents' or p == 'altevents': 448 param = "std::vector<SetSpeedEvent>{\n" 449 for i, e in enumerate(eActions[p]): 450 aEvent = getEvent(zNum, zCond, e, events) 451 if not aEvent: 452 continue 453 if (i+1) != len(eActions[p]): 454 param += genEvent(aEvent) + ",\n" 455 else: 456 param += genEvent(aEvent) + "\n" 457 param += "\t}" 458 elif p == 'property': 459 if isinstance(eActions[p], str) or \ 460 "string" in str(eActions[p]['type']).lower(): 461 param += ( 462 str(eActions[p]['type']).lower() + 463 ">(\"" + str(eActions[p]) + "\")") 464 else: 465 param += ( 466 str(eActions[p]['type']).lower() + 467 ">(" + str(eActions[p]['value']).lower() + ")") 468 else: 469 # Default type to 'size_t' when not given 470 param += ("size_t>(" + str(eActions[p]).lower() + ")") 471 else: 472 if p == 'timer': 473 t = getTimer(eActions[p]) 474 param = ( 475 "TimerConf{" + t['interval'] + "," + 476 t['type'] + "}") 477 else: 478 param += (str(eActions[p]['type']).lower() + ">(") 479 if p != 'map': 480 if isinstance(eActions[p]['value'], str) or \ 481 "string" in str(eActions[p]['type']).lower(): 482 param += \ 483 "\"" + str(eActions[p]['value']) + "\")" 484 else: 485 param += \ 486 str(eActions[p]['value']).lower() + ")" 487 else: 488 param += ( 489 str(eActions[p]['type']).lower() + 490 convertToMap(str(eActions[p]['value'])) + ")") 491 params.append(param) 492 actions['parameters'] = params 493 action.append(actions) 494 return action 495 496 497def getEvent(zone_num, zone_conditions, e, events_data): 498 """ 499 Parses the sections of an event and populates the properties 500 that construct an event within the generated source. 501 """ 502 event = {} 503 504 # Add set speed event name 505 event['name'] = e['name'] 506 507 # Add set speed event groups 508 event['groups'] = getGroups(zone_num, zone_conditions, e, events_data) 509 510 # Add optional set speed actions and function parameters 511 event['action'] = [] 512 if ('actions' in e) and \ 513 (e['actions'] is not None): 514 # List of dicts containing the list of groups and list of actions 515 sseActions = [] 516 eActions = getActions(zone_num, zone_conditions, e, e, events_data) 517 for eAction in eActions: 518 # Skip events that have no groups defined for the event or actions 519 if not event['groups'] and not eAction['groups']: 520 continue 521 # Find group in sseActions 522 grpExists = False 523 for sseDict in sseActions: 524 if eAction['groups'] == sseDict['groups']: 525 # Extend 'actions' list 526 del eAction['groups'] 527 sseDict['actions'].append(eAction) 528 grpExists = True 529 break 530 if not grpExists: 531 grps = eAction['groups'] 532 del eAction['groups'] 533 actList = [] 534 actList.append(eAction) 535 sseActions.append({'groups': grps, 'actions': actList}) 536 event['action'] = sseActions 537 538 # Add event triggers 539 event['triggers'] = {} 540 for trig in e['triggers']: 541 triggers = [] 542 if (trig['name'] == "timer"): 543 event['triggers']['timer'] = getTimer(trig) 544 elif (trig['name'] == "signal"): 545 if ('signals' not in event['triggers']): 546 event['triggers']['signals'] = [] 547 triggers = getSignal(event['groups'], trig, events_data) 548 event['triggers']['signals'].extend(triggers) 549 elif (trig['name'] == "init"): 550 triggers = getInit(event['groups'], trig, events_data) 551 event['triggers']['init'] = triggers 552 553 return event 554 555 556def addPrecondition(zNum, zCond, event, events_data): 557 """ 558 Parses the precondition section of an event and populates the necessary 559 structures to generate a precondition for a set speed event. 560 """ 561 precond = {} 562 563 # Add set speed event precondition name 564 precond['pcname'] = event['name'] 565 566 # Add set speed event precondition group 567 precond['pcgrps'] = getGroups(zNum, 568 zCond, 569 event['precondition'], 570 events_data) 571 572 # Add set speed event precondition actions 573 pc = [] 574 pcs = {} 575 pcs['name'] = event['precondition']['name'] 576 epc = next(p for p in events_data['preconditions'] 577 if p['name'] == event['precondition']['name']) 578 params = [] 579 for p in epc['parameters']: 580 param = {} 581 if p == 'groups': 582 param['type'] = "std::vector<PrecondGroup>" 583 param['open'] = "{" 584 param['close'] = "}" 585 values = [] 586 for group in precond['pcgrps']: 587 for pcgrp in group['members']: 588 value = {} 589 value['value'] = ( 590 "PrecondGroup{\"" + 591 str(pcgrp['object']) + "\",\"" + 592 str(pcgrp['interface']) + "\",\"" + 593 str(pcgrp['property']) + "\"," + 594 "static_cast<" + 595 str(pcgrp['type']).lower() + ">") 596 if isinstance(pcgrp['value'], str) or \ 597 "string" in str(pcgrp['type']).lower(): 598 value['value'] += ("(" + str(pcgrp['value']) + ")}") 599 else: 600 value['value'] += \ 601 ("(" + str(pcgrp['value']).lower() + ")}") 602 values.append(value) 603 param['values'] = values 604 params.append(param) 605 pcs['params'] = params 606 pc.append(pcs) 607 precond['pcact'] = pc 608 609 pcevents = [] 610 for pce in event['precondition']['events']: 611 pcevent = getEvent(zNum, zCond, pce, events_data) 612 if not pcevent: 613 continue 614 pcevents.append(pcevent) 615 precond['pcevts'] = pcevents 616 617 # Add precondition event triggers 618 precond['triggers'] = {} 619 for trig in event['precondition']['triggers']: 620 triggers = [] 621 if (trig['name'] == "timer"): 622 precond['triggers']['pctime'] = getTimer(trig) 623 elif (trig['name'] == "signal"): 624 if ('pcsigs' not in precond['triggers']): 625 precond['triggers']['pcsigs'] = [] 626 triggers = getSignal(precond['pcgrps'], trig, events_data) 627 precond['triggers']['pcsigs'].extend(triggers) 628 elif (trig['name'] == "init"): 629 triggers = getInit(precond['pcgrps'], trig, events_data) 630 precond['triggers']['init'] = triggers 631 632 return precond 633 634 635def getEventsInZone(zone_num, zone_conditions, events_data): 636 """ 637 Constructs the event entries defined for each zone using the events yaml 638 provided. 639 """ 640 events = [] 641 642 if 'events' in events_data: 643 for e in events_data['events']: 644 event = {} 645 646 # Add precondition if given 647 if ('precondition' in e) and \ 648 (e['precondition'] is not None): 649 event['pc'] = addPrecondition(zone_num, 650 zone_conditions, 651 e, 652 events_data) 653 else: 654 event = getEvent(zone_num, zone_conditions, e, events_data) 655 # Remove empty events and events that have 656 # no groups defined for the event or any of the actions 657 if not event or \ 658 (not event['groups'] and 659 all(not a['groups'] for a in event['action'])): 660 continue 661 events.append(event) 662 663 return events 664 665 666def getFansInZone(zone_num, profiles, fan_data): 667 """ 668 Parses the fan definition YAML files to find the fans 669 that match both the zone passed in and one of the 670 cooling profiles. 671 """ 672 673 fans = [] 674 675 for f in fan_data['fans']: 676 677 if zone_num != f['cooling_zone']: 678 continue 679 680 # 'cooling_profile' is optional (use 'all' instead) 681 if f.get('cooling_profile') is None: 682 profile = "all" 683 else: 684 profile = f['cooling_profile'] 685 686 if profile not in profiles: 687 continue 688 689 fan = {} 690 fan['name'] = f['inventory'] 691 fan['sensors'] = f['sensors'] 692 fan['target_interface'] = f.get( 693 'target_interface', 694 'xyz.openbmc_project.Control.FanSpeed') 695 fans.append(fan) 696 697 return fans 698 699 700def getIfacesInZone(zone_ifaces): 701 """ 702 Parse given interfaces for a zone for associating a zone with an interface 703 and set any properties listed to defined values upon fan control starting 704 on the zone. 705 """ 706 707 ifaces = [] 708 for i in zone_ifaces: 709 iface = {} 710 # Interface name not needed yet for fan zones but 711 # may be necessary as more interfaces are extended by the zones 712 iface['name'] = i['name'] 713 714 if ('properties' in i) and \ 715 (i['properties'] is not None): 716 props = [] 717 for p in i['properties']: 718 prop = {} 719 prop['name'] = p['name'] 720 prop['func'] = str(p['name']).lower() 721 prop['type'] = parse_cpp_type(p['type']) 722 if ('persist' in p): 723 persist = p['persist'] 724 if (persist is not None): 725 if (isinstance(persist, bool)): 726 prop['persist'] = 'true' if persist else 'false' 727 else: 728 prop['persist'] = 'false' 729 vals = [] 730 for v in p['values']: 731 val = v['value'] 732 if (val is not None): 733 if (isinstance(val, bool)): 734 # Convert True/False to 'true'/'false' 735 val = 'true' if val else 'false' 736 elif (isinstance(val, str)): 737 # Wrap strings with double-quotes 738 val = "\"" + val + "\"" 739 vals.append(val) 740 prop['values'] = vals 741 props.append(prop) 742 iface['props'] = props 743 ifaces.append(iface) 744 745 return ifaces 746 747 748def getConditionInZoneConditions(zone_condition, zone_conditions_data): 749 """ 750 Parses the zone conditions definition YAML files to find the condition 751 that match both the zone condition passed in. 752 """ 753 754 condition = {} 755 756 for c in zone_conditions_data['conditions']: 757 758 if zone_condition != c['name']: 759 continue 760 condition['type'] = c['type'] 761 properties = [] 762 for p in c['properties']: 763 property = {} 764 property['property'] = p['property'] 765 property['interface'] = p['interface'] 766 property['path'] = p['path'] 767 property['type'] = p['type'].lower() 768 property['value'] = str(p['value']).lower() 769 properties.append(property) 770 condition['properties'] = properties 771 772 return condition 773 774 775def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data): 776 """ 777 Combines the zone definition YAML and fan 778 definition YAML to create a data structure defining 779 the fan cooling zones. 780 """ 781 782 zone_groups = [] 783 784 for group in zone_data: 785 conditions = [] 786 # zone conditions are optional 787 if 'zone_conditions' in group and group['zone_conditions'] is not None: 788 for c in group['zone_conditions']: 789 790 if not zone_conditions_data: 791 sys.exit("No zone_conditions YAML file but " + 792 "zone_conditions used in zone YAML") 793 794 condition = getConditionInZoneConditions(c['name'], 795 zone_conditions_data) 796 797 if not condition: 798 sys.exit("Missing zone condition " + c['name']) 799 800 conditions.append(condition) 801 802 zone_group = {} 803 zone_group['conditions'] = conditions 804 805 zones = [] 806 for z in group['zones']: 807 zone = {} 808 809 # 'zone' is required 810 if ('zone' not in z) or (z['zone'] is None): 811 sys.exit("Missing fan zone number in " + zone_yaml) 812 813 zone['num'] = z['zone'] 814 815 zone['full_speed'] = z['full_speed'] 816 817 zone['default_floor'] = z['default_floor'] 818 819 # 'increase_delay' is optional (use 0 by default) 820 key = 'increase_delay' 821 zone[key] = z.setdefault(key, 0) 822 823 # 'decrease_interval' is optional (use 0 by default) 824 key = 'decrease_interval' 825 zone[key] = z.setdefault(key, 0) 826 827 # 'cooling_profiles' is optional (use 'all' instead) 828 if ('cooling_profiles' not in z) or \ 829 (z['cooling_profiles'] is None): 830 profiles = ["all"] 831 else: 832 profiles = z['cooling_profiles'] 833 834 # 'interfaces' is optional (no default) 835 ifaces = [] 836 if ('interfaces' in z) and \ 837 (z['interfaces'] is not None): 838 ifaces = getIfacesInZone(z['interfaces']) 839 840 fans = getFansInZone(z['zone'], profiles, fan_data) 841 events = getEventsInZone(z['zone'], group['zone_conditions'], 842 events_data) 843 844 if len(fans) == 0: 845 sys.exit("Didn't find any fans in zone " + str(zone['num'])) 846 847 if (ifaces): 848 zone['ifaces'] = ifaces 849 zone['fans'] = fans 850 zone['events'] = events 851 zones.append(zone) 852 853 zone_group['zones'] = zones 854 zone_groups.append(zone_group) 855 856 return zone_groups 857 858 859if __name__ == '__main__': 860 parser = ArgumentParser( 861 description="Phosphor fan zone definition parser") 862 863 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml', 864 default="example/zones.yaml", 865 help='fan zone definitional yaml') 866 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml', 867 default="example/fans.yaml", 868 help='fan definitional yaml') 869 parser.add_argument('-e', '--events_yaml', dest='events_yaml', 870 help='events to set speeds yaml') 871 parser.add_argument('-c', '--zone_conditions_yaml', 872 dest='zone_conditions_yaml', 873 help='conditions to determine zone yaml') 874 parser.add_argument('-o', '--output_dir', dest='output_dir', 875 default=".", 876 help='output directory') 877 args = parser.parse_args() 878 879 if not args.zone_yaml or not args.fan_yaml: 880 parser.print_usage() 881 sys.exit(1) 882 883 with open(args.zone_yaml, 'r') as zone_input: 884 zone_data = yaml.safe_load(zone_input) or {} 885 886 with open(args.fan_yaml, 'r') as fan_input: 887 fan_data = yaml.safe_load(fan_input) or {} 888 889 events_data = {} 890 if args.events_yaml: 891 with open(args.events_yaml, 'r') as events_input: 892 events_data = yaml.safe_load(events_input) or {} 893 894 zone_conditions_data = {} 895 if args.zone_conditions_yaml: 896 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input: 897 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {} 898 899 zone_config = buildZoneData(zone_data.get('zone_configuration', {}), 900 fan_data, events_data, zone_conditions_data) 901 902 manager_config = zone_data.get('manager_configuration', {}) 903 904 if manager_config.get('power_on_delay') is None: 905 manager_config['power_on_delay'] = 0 906 907 tmpls_dir = os.path.join( 908 os.path.dirname(os.path.realpath(__file__)), 909 "templates") 910 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp") 911 if sys.version_info < (3, 0): 912 lkup = TemplateLookup( 913 directories=tmpls_dir.split(), 914 disable_unicode=True) 915 else: 916 lkup = TemplateLookup( 917 directories=tmpls_dir.split()) 918 tmpl = lkup.get_template('fan_zone_defs.mako.cpp') 919 with open(output_file, 'w') as output: 920 output.write(tmpl.render(zones=zone_config, 921 mgr_data=manager_config)) 922