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