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