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