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