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 convertToMap(listOfDict): 17 """ 18 Converts a list of dictionary entries to a std::map initialization list. 19 """ 20 listOfDict = listOfDict.replace('\'', '\"') 21 listOfDict = listOfDict.replace('[', '{') 22 listOfDict = listOfDict.replace(']', '}') 23 listOfDict = listOfDict.replace(':', ',') 24 return listOfDict 25 26 27def genEvent(event): 28 """ 29 Generates the source code of an event and returns it as a string 30 """ 31 e = "SetSpeedEvent{\n" 32 33 e += "Group{\n" 34 for group in event['groups']: 35 for member in group['members']: 36 e += "{\n" 37 e += "\"" + member['object'] + "\",\n" 38 e += "{\"" + member['interface'] + "\",\n" 39 e += " \"" + member['property'] + "\"}\n" 40 e += "},\n" 41 e += "},\n" 42 43 e += "std::vector<Action>{\n" 44 for a in event['action']: 45 if len(a['parameters']) != 0: 46 e += "make_action(action::" + a['name'] + "(\n" 47 else: 48 e += "make_action(action::" + a['name'] + "\n" 49 for i, p in enumerate(a['parameters']): 50 if (i+1) != len(a['parameters']): 51 e += p + ",\n" 52 else: 53 e += p + ")\n" 54 e += "),\n" 55 e += "},\n" 56 57 e += "TimerConf{\n" 58 e += "\t" + event['timer']['interval'] + ",\n" 59 e += "\t" + event['timer']['type'] + "\n" 60 e += "},\n" 61 62 e += "std::vector<Signal>{\n" 63 for s in event['signals']: 64 e += "Signal{\n" 65 e += "match::" + s['match'] + "(\n" 66 for i, mp in enumerate(s['mparams']): 67 if (i+1) != len(s['mparams']): 68 e += "\"" + mp + "\",\n" 69 else: 70 e += "\"" + mp + "\"\n" 71 e += "),\n" 72 e += "make_handler(\n" 73 if ('type' in s['sparams']) and (s['sparams']['type'] is not None): 74 e += s['signal'] + "<" + s['sparams']['type'] + ">(\n" 75 else: 76 e += s['signal'] + "(\n" 77 for sp in s['sparams']['params']: 78 e += s['sparams'][sp] + ",\n" 79 if ('type' in s['hparams']) and (s['hparams']['type'] is not None): 80 e += ("handler::" + s['handler'] + 81 "<" + s['hparams']['type'] + ">(\n") 82 else: 83 e += "handler::" + s['handler'] + "(\n)" 84 for i, hp in enumerate(s['hparams']['params']): 85 if (i+1) != len(s['hparams']['params']): 86 e += s['hparams'][hp] + ",\n" 87 else: 88 e += s['hparams'][hp] + "\n" 89 e += "))\n" 90 e += ")\n" 91 e += "},\n" 92 e += "}\n" 93 94 e += "}" 95 96 return e 97 98 99def getGroups(zNum, zCond, edata, events): 100 """ 101 Extract and construct the groups for the given event. 102 """ 103 groups = [] 104 for eGroups in edata['groups']: 105 if ('zone_conditions' in eGroups) and \ 106 (eGroups['zone_conditions'] is not None): 107 # Zone conditions are optional in the events yaml but skip 108 # if this event's condition is not in this zone's conditions 109 if all('name' in z and z['name'] is not None and 110 not any(c['name'] == z['name'] for c in zCond) 111 for z in eGroups['zone_conditions']): 112 continue 113 114 # Zone numbers are optional in the events yaml but skip if this 115 # zone's zone number is not in the event's zone numbers 116 if all('zones' in z and z['zones'] is not None and 117 zNum not in z['zones'] 118 for z in eGroups['zone_conditions']): 119 continue 120 eGroup = next(g for g in events['groups'] 121 if g['name'] == eGroups['name']) 122 123 group = {} 124 members = [] 125 group['name'] = eGroup['name'] 126 for m in eGroup['members']: 127 member = {} 128 member['path'] = eGroup['type'] 129 member['object'] = (eGroup['type'] + m) 130 member['interface'] = eGroups['interface'] 131 member['property'] = eGroups['property']['name'] 132 member['type'] = eGroups['property']['type'] 133 # Add expected group member's property value if given 134 if ('value' in eGroups['property']) and \ 135 (eGroups['property']['value'] is not None): 136 if isinstance(eGroups['property']['value'], str) or \ 137 "string" in str(member['type']).lower(): 138 member['value'] = ( 139 "\"" + eGroups['property']['value'] + "\"") 140 else: 141 member['value'] = eGroups['property']['value'] 142 members.append(member) 143 group['members'] = members 144 groups.append(group) 145 return groups 146 147 148def getActions(zNum, zCond, edata, actions, events): 149 """ 150 Extracts and constructs the make_action function call for 151 all the actions within the given event. 152 """ 153 action = [] 154 for eActions in actions['actions']: 155 actions = {} 156 eAction = next(a for a in events['actions'] 157 if a['name'] == eActions['name']) 158 actions['name'] = eAction['name'] 159 params = [] 160 if ('parameters' in eAction) and \ 161 (eAction['parameters'] is not None): 162 for p in eAction['parameters']: 163 param = "static_cast<" 164 if type(eActions[p]) is not dict: 165 if p == 'actions': 166 param = "std::vector<Action>{" 167 pActs = getActions(zNum, 168 zCond, 169 edata, 170 eActions, 171 events) 172 for a in pActs: 173 if (len(a['parameters']) != 0): 174 param += ( 175 "make_action(action::" + 176 a['name'] + 177 "(\n") 178 for i, ap in enumerate(a['parameters']): 179 if (i+1) != len(a['parameters']): 180 param += (ap + ",") 181 else: 182 param += (ap + ")") 183 else: 184 param += ("make_action(action::" + a['name']) 185 param += ")," 186 param += "}" 187 elif p == 'defevents' or p == 'altevents': 188 param = "std::vector<SetSpeedEvent>{\n" 189 for i, e in enumerate(eActions[p]): 190 aEvent = getEvent(zNum, zCond, e, events) 191 if not aEvent: 192 continue 193 if (i+1) != len(eActions[p]): 194 param += genEvent(aEvent) + ",\n" 195 else: 196 param += genEvent(aEvent) + "\n" 197 param += "\t}" 198 elif p == 'property': 199 if isinstance(eActions[p], str) or \ 200 "string" in str(eActions[p]['type']).lower(): 201 param += ( 202 str(eActions[p]['type']).lower() + 203 ">(\"" + str(eActions[p]) + "\")") 204 else: 205 param += ( 206 str(eActions[p]['type']).lower() + 207 ">(" + str(eActions[p]['value']).lower() + ")") 208 else: 209 # Default type to 'size_t' when not given 210 param += ("size_t>(" + str(eActions[p]).lower() + ")") 211 else: 212 if p == 'timer': 213 param = ( 214 "TimerConf{static_cast<std::chrono::seconds>(" + 215 str(eActions[p]['delay']) + "), " + 216 "TimerType::" + 217 str(eActions[p]['type']) + "}") 218 else: 219 param += (str(eActions[p]['type']).lower() + ">(") 220 if p != 'map': 221 if isinstance(eActions[p]['value'], str) or \ 222 "string" in str(eActions[p]['type']).lower(): 223 param += \ 224 "\"" + str(eActions[p]['value']) + "\")" 225 else: 226 param += \ 227 str(eActions[p]['value']).lower() + ")" 228 else: 229 param += ( 230 str(eActions[p]['type']).lower() + 231 convertToMap(str(eActions[p]['value'])) + ")") 232 params.append(param) 233 actions['parameters'] = params 234 action.append(actions) 235 return action 236 237 238def getEvent(zone_num, zone_conditions, e, events_data): 239 """ 240 Parses the sections of an event and populates the properties 241 that construct an event within the generated source. 242 """ 243 event = {} 244 245 # Add set speed event groups 246 grps = getGroups(zone_num, zone_conditions, e, events_data) 247 if not grps: 248 return 249 event['groups'] = grps 250 251 # Add optional set speed actions and function parameters 252 event['action'] = [] 253 if ('actions' in e) and \ 254 (e['actions'] is not None): 255 event['action'] = getActions(zone_num, 256 zone_conditions, 257 e, 258 e, 259 events_data) 260 261 # Add signal handlers 262 signals = [] 263 for group in event['groups']: 264 for member in group['members']: 265 for eMatches in e['matches']: 266 signal = {} 267 eMatch = next(m for m in events_data['matches'] 268 if m['name'] == eMatches['name']) 269 signal['match'] = eMatch['name'] 270 params = [] 271 if ('parameters' in eMatch) and \ 272 (eMatch['parameters'] is not None): 273 for p in eMatch['parameters']: 274 params.append(member[str(p)]) 275 signal['mparams'] = params 276 if ('parameters' in eMatch['signal']) and \ 277 (eMatch['signal']['parameters'] is not None): 278 eSignal = eMatch['signal'] 279 else: 280 eSignal = next(s for s in events_data['signals'] 281 if s['name'] == eMatch['signal']) 282 signal['signal'] = eSignal['name'] 283 sparams = {} 284 if ('parameters' in eSignal) and \ 285 (eSignal['parameters'] is not None): 286 splist = [] 287 for p in eSignal['parameters']: 288 sp = str(p) 289 if (sp != 'type'): 290 splist.append(sp) 291 if (sp != 'group'): 292 sparams[sp] = "\"" + member[sp] + "\"" 293 else: 294 sparams[sp] = "Group{\n" 295 for m in group['members']: 296 sparams[sp] += ( 297 "{\n" + 298 "\"" + str(m['object']) + "\",\n" + 299 "{\"" + str(m['interface']) + "\"," + 300 "\"" + str(m['property']) + "\"}\n" + 301 "},\n") 302 sparams[sp] += "}" 303 else: 304 sparams[sp] = member[sp] 305 sparams['params'] = splist 306 signal['sparams'] = sparams 307 # Add signal handler 308 eHandler = next(h for h in events_data['handlers'] 309 if h['name'] == eSignal['handler']) 310 signal['handler'] = eHandler['name'] 311 hparams = {} 312 if ('parameters' in eHandler) and \ 313 (eHandler['parameters'] is not None): 314 hplist = [] 315 for p in eHandler['parameters']: 316 hp = str(p) 317 if (hp != 'type'): 318 hplist.append(hp) 319 if (hp != 'group'): 320 hparams[hp] = "\"" + member[hp] + "\"" 321 else: 322 hparams[hp] = "Group{\n" 323 for m in group['members']: 324 hparams[hp] += ( 325 "{\n" + 326 "\"" + str(m['object']) + "\",\n" + 327 "{\"" + str(m['interface']) + "\"," + 328 "\"" + str(m['property']) + "\"}\n" + 329 "},\n") 330 hparams[hp] += "}" 331 else: 332 hparams[hp] = member[hp] 333 hparams['params'] = hplist 334 signal['hparams'] = hparams 335 signals.append(signal) 336 event['signals'] = signals 337 338 # Add optional action call timer 339 timer = {} 340 interval = "static_cast<std::chrono::seconds>" 341 if ('timer' in e) and \ 342 (e['timer'] is not None): 343 timer['interval'] = (interval + 344 "(" + 345 str(e['timer']['interval']) + 346 ")") 347 else: 348 timer['interval'] = (interval + 349 "(" + str(0) + ")") 350 timer['type'] = "TimerType::repeating" 351 event['timer'] = timer 352 353 return event 354 355 356def addPrecondition(zNum, zCond, event, events_data): 357 """ 358 Parses the precondition section of an event and populates the necessary 359 structures to generate a precondition for a set speed event. 360 """ 361 precond = {} 362 # Add set speed event precondition group 363 grps = getGroups(zNum, zCond, event['precondition'], events_data) 364 if not grps: 365 return 366 precond['pcgrps'] = grps 367 368 # Add set speed event precondition actions 369 pc = [] 370 pcs = {} 371 pcs['name'] = event['precondition']['name'] 372 epc = next(p for p in events_data['preconditions'] 373 if p['name'] == event['precondition']['name']) 374 params = [] 375 for p in epc['parameters']: 376 param = {} 377 if p == 'groups': 378 param['type'] = "std::vector<PrecondGroup>" 379 param['open'] = "{" 380 param['close'] = "}" 381 values = [] 382 for group in precond['pcgrps']: 383 for pcgrp in group['members']: 384 value = {} 385 value['value'] = ( 386 "PrecondGroup{\"" + 387 str(pcgrp['object']) + "\",\"" + 388 str(pcgrp['interface']) + "\",\"" + 389 str(pcgrp['property']) + "\"," + 390 "static_cast<" + 391 str(pcgrp['type']).lower() + ">") 392 if isinstance(pcgrp['value'], str) or \ 393 "string" in str(pcgrp['type']).lower(): 394 value['value'] += ("(" + str(pcgrp['value']) + ")}") 395 else: 396 value['value'] += \ 397 ("(" + str(pcgrp['value']).lower() + ")}") 398 values.append(value) 399 param['values'] = values 400 params.append(param) 401 pcs['params'] = params 402 pc.append(pcs) 403 precond['pcact'] = pc 404 405 pcevents = [] 406 for pce in event['precondition']['events']: 407 pcevent = getEvent(zNum, zCond, pce, events_data) 408 if not pcevent: 409 continue 410 pcevents.append(pcevent) 411 precond['pcevts'] = pcevents 412 413 # Add precondition signal handlers 414 signals = [] 415 for group in precond['pcgrps']: 416 for member in group['members']: 417 for eMatches in event['precondition']['matches']: 418 signal = {} 419 eMatch = next(m for m in events_data['matches'] 420 if m['name'] == eMatches['name']) 421 signal['match'] = eMatch['name'] 422 params = [] 423 if ('parameters' in eMatch) and \ 424 (eMatch['parameters'] is not None): 425 for p in eMatch['parameters']: 426 params.append(member[str(p)]) 427 signal['mparams'] = params 428 if ('parameters' in eMatch['signal']) and \ 429 (eMatch['signal']['parameters'] is not None): 430 eSignal = eMatch['signal'] 431 else: 432 eSignal = next(s for s in events_data['signals'] 433 if s['name'] == eMatch['signal']) 434 signal['signal'] = eSignal['name'] 435 sparams = {} 436 if ('parameters' in eSignal) and \ 437 (eSignal['parameters'] is not None): 438 splist = [] 439 for p in eSignal['parameters']: 440 sp = str(p) 441 if (sp != 'type'): 442 splist.append(sp) 443 if (sp != 'group'): 444 sparams[sp] = "\"" + member[sp] + "\"" 445 else: 446 sparams[sp] = "Group{\n" 447 for m in group: 448 sparams[sp] += ( 449 "{\n" + 450 "\"" + str(m['object']) + "\",\n" + 451 "{\"" + str(m['interface']) + "\"," + 452 "\"" + str(m['property']) + "\"}\n" + 453 "},\n") 454 sparams[sp] += "}" 455 else: 456 sparams[sp] = member[sp] 457 sparams['params'] = splist 458 signal['sparams'] = sparams 459 # Add signal handler 460 eHandler = next(h for h in events_data['handlers'] 461 if h['name'] == eSignal['handler']) 462 signal['handler'] = eHandler['name'] 463 hparams = {} 464 if ('parameters' in eHandler) and \ 465 (eHandler['parameters'] is not None): 466 hplist = [] 467 for p in eHandler['parameters']: 468 hp = str(p) 469 if (hp != 'type'): 470 hplist.append(hp) 471 if (hp != 'group'): 472 hparams[hp] = "\"" + member[hp] + "\"" 473 else: 474 hparams[hp] = "Group{\n" 475 for m in group: 476 hparams[hp] += ( 477 "{\n" + 478 "\"" + str(m['object']) + "\",\n" + 479 "{\"" + str(m['interface']) + "\"," + 480 "\"" + str(m['property']) + "\"}\n" + 481 "},\n") 482 hparams[hp] += "}" 483 else: 484 hparams[hp] = member[hp] 485 hparams['params'] = hplist 486 signal['hparams'] = hparams 487 signals.append(signal) 488 precond['pcsigs'] = signals 489 490 # Add optional action call timer 491 timer = {} 492 interval = "static_cast<std::chrono::seconds>" 493 if ('timer' in event['precondition']) and \ 494 (event['precondition']['timer'] is not None): 495 timer['interval'] = (interval + 496 "(" + 497 str(event['precondition']['timer']['interval']) + 498 ")") 499 else: 500 timer['interval'] = (interval + 501 "(" + str(0) + ")") 502 timer['type'] = "TimerType::repeating" 503 precond['pctime'] = timer 504 505 return precond 506 507 508def getEventsInZone(zone_num, zone_conditions, events_data): 509 """ 510 Constructs the event entries defined for each zone using the events yaml 511 provided. 512 """ 513 events = [] 514 515 if 'events' in events_data: 516 for e in events_data['events']: 517 event = {} 518 # Add precondition if given 519 if ('precondition' in e) and \ 520 (e['precondition'] is not None): 521 event['pc'] = addPrecondition(zone_num, 522 zone_conditions, 523 e, 524 events_data) 525 else: 526 event = getEvent(zone_num, zone_conditions, e, events_data) 527 if not event: 528 continue 529 events.append(event) 530 531 return events 532 533 534def getFansInZone(zone_num, profiles, fan_data): 535 """ 536 Parses the fan definition YAML files to find the fans 537 that match both the zone passed in and one of the 538 cooling profiles. 539 """ 540 541 fans = [] 542 543 for f in fan_data['fans']: 544 545 if zone_num != f['cooling_zone']: 546 continue 547 548 # 'cooling_profile' is optional (use 'all' instead) 549 if f.get('cooling_profile') is None: 550 profile = "all" 551 else: 552 profile = f['cooling_profile'] 553 554 if profile not in profiles: 555 continue 556 557 fan = {} 558 fan['name'] = f['inventory'] 559 fan['sensors'] = f['sensors'] 560 fan['target_interface'] = f.get( 561 'target_interface', 562 'xyz.openbmc_project.Control.FanSpeed') 563 fans.append(fan) 564 565 return fans 566 567 568def getConditionInZoneConditions(zone_condition, zone_conditions_data): 569 """ 570 Parses the zone conditions definition YAML files to find the condition 571 that match both the zone condition passed in. 572 """ 573 574 condition = {} 575 576 for c in zone_conditions_data['conditions']: 577 578 if zone_condition != c['name']: 579 continue 580 condition['type'] = c['type'] 581 properties = [] 582 for p in c['properties']: 583 property = {} 584 property['property'] = p['property'] 585 property['interface'] = p['interface'] 586 property['path'] = p['path'] 587 property['type'] = p['type'].lower() 588 property['value'] = str(p['value']).lower() 589 properties.append(property) 590 condition['properties'] = properties 591 592 return condition 593 594 595def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data): 596 """ 597 Combines the zone definition YAML and fan 598 definition YAML to create a data structure defining 599 the fan cooling zones. 600 """ 601 602 zone_groups = [] 603 604 for group in zone_data: 605 conditions = [] 606 # zone conditions are optional 607 if 'zone_conditions' in group and group['zone_conditions'] is not None: 608 for c in group['zone_conditions']: 609 610 if not zone_conditions_data: 611 sys.exit("No zone_conditions YAML file but " + 612 "zone_conditions used in zone YAML") 613 614 condition = getConditionInZoneConditions(c['name'], 615 zone_conditions_data) 616 617 if not condition: 618 sys.exit("Missing zone condition " + c['name']) 619 620 conditions.append(condition) 621 622 zone_group = {} 623 zone_group['conditions'] = conditions 624 625 zones = [] 626 for z in group['zones']: 627 zone = {} 628 629 # 'zone' is required 630 if ('zone' not in z) or (z['zone'] is None): 631 sys.exit("Missing fan zone number in " + zone_yaml) 632 633 zone['num'] = z['zone'] 634 635 zone['full_speed'] = z['full_speed'] 636 637 zone['default_floor'] = z['default_floor'] 638 639 # 'increase_delay' is optional (use 0 by default) 640 key = 'increase_delay' 641 zone[key] = z.setdefault(key, 0) 642 643 # 'decrease_interval' is optional (use 0 by default) 644 key = 'decrease_interval' 645 zone[key] = z.setdefault(key, 0) 646 647 # 'cooling_profiles' is optional (use 'all' instead) 648 if ('cooling_profiles' not in z) or \ 649 (z['cooling_profiles'] is None): 650 profiles = ["all"] 651 else: 652 profiles = z['cooling_profiles'] 653 654 fans = getFansInZone(z['zone'], profiles, fan_data) 655 events = getEventsInZone(z['zone'], group['zone_conditions'], 656 events_data) 657 658 if len(fans) == 0: 659 sys.exit("Didn't find any fans in zone " + str(zone['num'])) 660 661 zone['fans'] = fans 662 zone['events'] = events 663 zones.append(zone) 664 665 zone_group['zones'] = zones 666 zone_groups.append(zone_group) 667 668 return zone_groups 669 670 671if __name__ == '__main__': 672 parser = ArgumentParser( 673 description="Phosphor fan zone definition parser") 674 675 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml', 676 default="example/zones.yaml", 677 help='fan zone definitional yaml') 678 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml', 679 default="example/fans.yaml", 680 help='fan definitional yaml') 681 parser.add_argument('-e', '--events_yaml', dest='events_yaml', 682 help='events to set speeds yaml') 683 parser.add_argument('-c', '--zone_conditions_yaml', 684 dest='zone_conditions_yaml', 685 help='conditions to determine zone yaml') 686 parser.add_argument('-o', '--output_dir', dest='output_dir', 687 default=".", 688 help='output directory') 689 args = parser.parse_args() 690 691 if not args.zone_yaml or not args.fan_yaml: 692 parser.print_usage() 693 sys.exit(1) 694 695 with open(args.zone_yaml, 'r') as zone_input: 696 zone_data = yaml.safe_load(zone_input) or {} 697 698 with open(args.fan_yaml, 'r') as fan_input: 699 fan_data = yaml.safe_load(fan_input) or {} 700 701 events_data = {} 702 if args.events_yaml: 703 with open(args.events_yaml, 'r') as events_input: 704 events_data = yaml.safe_load(events_input) or {} 705 706 zone_conditions_data = {} 707 if args.zone_conditions_yaml: 708 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input: 709 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {} 710 711 zone_config = buildZoneData(zone_data.get('zone_configuration', {}), 712 fan_data, events_data, zone_conditions_data) 713 714 manager_config = zone_data.get('manager_configuration', {}) 715 716 if manager_config.get('power_on_delay') is None: 717 manager_config['power_on_delay'] = 0 718 719 tmpls_dir = os.path.join( 720 os.path.dirname(os.path.realpath(__file__)), 721 "templates") 722 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp") 723 if sys.version_info < (3, 0): 724 lkup = TemplateLookup( 725 directories=tmpls_dir.split(), 726 disable_unicode=True) 727 else: 728 lkup = TemplateLookup( 729 directories=tmpls_dir.split()) 730 tmpl = lkup.get_template('fan_zone_defs.mako.cpp') 731 with open(output_file, 'w') as output: 732 output.write(tmpl.render(zones=zone_config, 733 mgr_data=manager_config)) 734