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