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