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 ${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 getEvent(zone_num, zone_conditions, e, events_data): 253 """ 254 Parses the sections of an event and populates the properties 255 that construct an event within the generated source. 256 """ 257 event = {} 258 # Zone numbers are optional in the events yaml but skip if this 259 # zone's zone number is not in the event's zone numbers 260 if all('zones' in z and 261 z['zones'] is not None and 262 zone_num not in z['zones'] 263 for z in e['zone_conditions']): 264 return 265 266 # Zone conditions are optional in the events yaml but skip 267 # if this event's condition is not in this zone's conditions 268 if all('name' in z and z['name'] is not None and 269 not any(c['name'] == z['name'] for c in zone_conditions) 270 for z in e['zone_conditions']): 271 return 272 273 # Add set speed event group 274 group = [] 275 groups = next(g for g in events_data['groups'] 276 if g['name'] == e['group']) 277 for member in groups['members']: 278 members = {} 279 members['object'] = (groups['type'] + 280 member) 281 members['interface'] = e['interface'] 282 members['property'] = e['property']['name'] 283 members['type'] = e['property']['type'] 284 group.append(members) 285 event['group'] = group 286 287 # Add set speed actions and function parameters 288 action = [] 289 for eActions in e['actions']: 290 actions = {} 291 eAction = next(a for a in events_data['actions'] 292 if a['name'] == eActions['name']) 293 actions['name'] = eAction['name'] 294 params = [] 295 if ('parameters' in eAction) and \ 296 (eAction['parameters'] is not None): 297 for p in eAction['parameters']: 298 param = {} 299 if type(eActions[p]) is not dict: 300 if p == 'property': 301 param['value'] = str(eActions[p]).lower() 302 param['type'] = str( 303 e['property']['type']).lower() 304 else: 305 # Default type to 'size_t' when not given 306 param['value'] = str(eActions[p]).lower() 307 param['type'] = 'size_t' 308 params.append(param) 309 else: 310 param['type'] = str(eActions[p]['type']).lower() 311 if p != 'map': 312 param['value'] = str( 313 eActions[p]['value']).lower() 314 else: 315 emap = convertToMap(str(eActions[p]['value'])) 316 param['value'] = param['type'] + emap 317 params.append(param) 318 actions['parameters'] = params 319 action.append(actions) 320 event['action'] = action 321 322 # Add signal handlers 323 signals = [] 324 for member in group: 325 for eMatches in e['matches']: 326 signal = {} 327 eMatch = next(m for m in events_data['matches'] 328 if m['name'] == eMatches['name']) 329 signal['match'] = eMatch['name'] 330 params = [] 331 if ('parameters' in eMatch) and \ 332 (eMatch['parameters'] is not None): 333 for p in eMatch['parameters']: 334 params.append(member[str(p)]) 335 signal['mparams'] = params 336 eSignal = next(s for s in events_data['signals'] 337 if s['name'] == eMatch['signal']) 338 signal['signal'] = eSignal['name'] 339 sparams = {} 340 if ('parameters' in eSignal) and \ 341 (eSignal['parameters'] is not None): 342 splist = [] 343 for p in eSignal['parameters']: 344 sp = str(p) 345 if (sp != 'type'): 346 splist.append(sp) 347 if (sp != 'group'): 348 sparams[sp] = "\"" + member[sp] + "\"" 349 else: 350 sparams[sp] = "Group{\n" 351 for m in group: 352 sparams[sp] += ( 353 "{\n" + 354 "\"" + str(m['object']) + "\",\n" + 355 "{\"" + str(m['interface']) + "\"," + 356 "\"" + str(m['property']) + "\"}\n" + 357 "},\n") 358 sparams[sp] += "}" 359 else: 360 sparams[sp] = member[sp] 361 sparams['params'] = splist 362 signal['sparams'] = sparams 363 # Add signal handler 364 eHandler = next(h for h in events_data['handlers'] 365 if h['name'] == eSignal['handler']) 366 signal['handler'] = eHandler['name'] 367 hparams = {} 368 if ('parameters' in eHandler) and \ 369 (eHandler['parameters'] is not None): 370 hplist = [] 371 for p in eHandler['parameters']: 372 hp = str(p) 373 if (hp != 'type'): 374 hplist.append(hp) 375 if (hp != 'group'): 376 hparams[hp] = "\"" + member[hp] + "\"" 377 else: 378 hparams[hp] = "Group{\n" 379 for m in group: 380 hparams[hp] += ( 381 "{\n" + 382 "\"" + str(m['object']) + "\",\n" + 383 "{\"" + str(m['interface']) + "\"," + 384 "\"" + str(m['property']) + "\"}\n" + 385 "},\n") 386 hparams[hp] += "}" 387 else: 388 hparams[hp] = member[hp] 389 hparams['params'] = hplist 390 signal['hparams'] = hparams 391 signals.append(signal) 392 event['signals'] = signals 393 394 # Add optional action call timer 395 timer = {} 396 interval = "static_cast<std::chrono::seconds>" 397 if ('timer' in e) and \ 398 (e['timer'] is not None): 399 timer['interval'] = (interval + 400 "(" + 401 str(e['timer']['interval']) + 402 ")") 403 else: 404 timer['interval'] = (interval + 405 "(" + str(0) + ")") 406 timer['type'] = "util::Timer::TimerType::repeating" 407 event['timer'] = timer 408 409 return event 410 411 412def addPrecondition(zNum, zCond, event, events_data): 413 """ 414 Parses the precondition section of an event and populates the necessary 415 structures to generate a precondition for a set speed event. 416 """ 417 precond = {} 418 # Add set speed event precondition group 419 group = [] 420 for grp in event['precondition']['groups']: 421 groups = next(g for g in events_data['groups'] 422 if g['name'] == grp['name']) 423 for member in groups['members']: 424 members = {} 425 members['object'] = (groups['type'] + 426 member) 427 members['interface'] = grp['interface'] 428 members['property'] = grp['property']['name'] 429 members['type'] = grp['property']['type'] 430 members['value'] = grp['property']['value'] 431 group.append(members) 432 precond['pcgrp'] = group 433 434 # Add set speed event precondition actions 435 pc = [] 436 pcs = {} 437 pcs['name'] = event['precondition']['name'] 438 epc = next(p for p in events_data['preconditions'] 439 if p['name'] == event['precondition']['name']) 440 params = [] 441 for p in epc['parameters']: 442 param = {} 443 if p == 'groups': 444 param['type'] = "std::vector<PrecondGroup>" 445 param['open'] = "{" 446 param['close'] = "}" 447 values = [] 448 for pcgrp in group: 449 value = {} 450 value['value'] = ( 451 "PrecondGroup{\"" + 452 str(pcgrp['object']) + "\",\"" + 453 str(pcgrp['interface']) + "\",\"" + 454 str(pcgrp['property']) + "\"," + 455 "static_cast<" + 456 str(pcgrp['type']).lower() + ">" + 457 "(" + str(pcgrp['value']).lower() + ")}") 458 values.append(value) 459 param['values'] = values 460 params.append(param) 461 pcs['params'] = params 462 pc.append(pcs) 463 precond['pcact'] = pc 464 465 pcevents = [] 466 for pce in event['precondition']['events']: 467 pcevent = getEvent(zNum, zCond, pce, events_data) 468 if not pcevent: 469 continue 470 pcevents.append(pcevent) 471 precond['pcevts'] = pcevents 472 473 # Add precondition signal handlers 474 signals = [] 475 for member in group: 476 for eMatches in event['precondition']['matches']: 477 signal = {} 478 eMatch = next(m for m in events_data['matches'] 479 if m['name'] == eMatches['name']) 480 signal['match'] = eMatch['name'] 481 params = [] 482 if ('parameters' in eMatch) and \ 483 (eMatch['parameters'] is not None): 484 for p in eMatch['parameters']: 485 params.append(member[str(p)]) 486 signal['mparams'] = params 487 eSignal = next(s for s in events_data['signals'] 488 if s['name'] == eMatch['signal']) 489 signal['signal'] = eSignal['name'] 490 sparams = {} 491 if ('parameters' in eSignal) and \ 492 (eSignal['parameters'] is not None): 493 splist = [] 494 for p in eSignal['parameters']: 495 sp = str(p) 496 if (sp != 'type'): 497 splist.append(sp) 498 if (sp != 'group'): 499 sparams[sp] = "\"" + member[sp] + "\"" 500 else: 501 sparams[sp] = "Group{\n" 502 for m in group: 503 sparams[sp] += ( 504 "{\n" + 505 "\"" + str(m['object']) + "\",\n" + 506 "{\"" + str(m['interface']) + "\"," + 507 "\"" + str(m['property']) + "\"}\n" + 508 "},\n") 509 sparams[sp] += "}" 510 else: 511 sparams[sp] = member[sp] 512 sparams['params'] = splist 513 signal['sparams'] = sparams 514 # Add signal handler 515 eHandler = next(h for h in events_data['handlers'] 516 if h['name'] == eSignal['handler']) 517 signal['handler'] = eHandler['name'] 518 hparams = {} 519 if ('parameters' in eHandler) and \ 520 (eHandler['parameters'] is not None): 521 hplist = [] 522 for p in eHandler['parameters']: 523 hp = str(p) 524 if (hp != 'type'): 525 hplist.append(hp) 526 if (hp != 'group'): 527 hparams[hp] = "\"" + member[hp] + "\"" 528 else: 529 hparams[hp] = "Group{\n" 530 for m in group: 531 hparams[hp] += ( 532 "{\n" + 533 "\"" + str(m['object']) + "\",\n" + 534 "{\"" + str(m['interface']) + "\"," + 535 "\"" + str(m['property']) + "\"}\n" + 536 "},\n") 537 hparams[hp] += "}" 538 else: 539 hparams[hp] = member[hp] 540 hparams['params'] = hplist 541 signal['hparams'] = hparams 542 signals.append(signal) 543 precond['pcsigs'] = signals 544 545 # Add optional action call timer 546 timer = {} 547 interval = "static_cast<std::chrono::seconds>" 548 if ('timer' in event['precondition']) and \ 549 (event['precondition']['timer'] is not None): 550 timer['interval'] = (interval + 551 "(" + 552 str(event['precondition']['timer']['interval']) + 553 ")") 554 else: 555 timer['interval'] = (interval + 556 "(" + str(0) + ")") 557 timer['type'] = "util::Timer::TimerType::repeating" 558 precond['pctime'] = timer 559 560 return precond 561 562 563def getEventsInZone(zone_num, zone_conditions, events_data): 564 """ 565 Constructs the event entries defined for each zone using the events yaml 566 provided. 567 """ 568 events = [] 569 570 if 'events' in events_data: 571 for e in events_data['events']: 572 event = {} 573 # Add precondition if given 574 if ('precondition' in e) and \ 575 (e['precondition'] is not None): 576 event['pc'] = addPrecondition(zone_num, 577 zone_conditions, 578 e, 579 events_data) 580 else: 581 event = getEvent(zone_num, zone_conditions, e, events_data) 582 if not event: 583 continue 584 events.append(event) 585 586 return events 587 588 589def getFansInZone(zone_num, profiles, fan_data): 590 """ 591 Parses the fan definition YAML files to find the fans 592 that match both the zone passed in and one of the 593 cooling profiles. 594 """ 595 596 fans = [] 597 598 for f in fan_data['fans']: 599 600 if zone_num != f['cooling_zone']: 601 continue 602 603 # 'cooling_profile' is optional (use 'all' instead) 604 if f.get('cooling_profile') is None: 605 profile = "all" 606 else: 607 profile = f['cooling_profile'] 608 609 if profile not in profiles: 610 continue 611 612 fan = {} 613 fan['name'] = f['inventory'] 614 fan['sensors'] = f['sensors'] 615 fans.append(fan) 616 617 return fans 618 619 620def getConditionInZoneConditions(zone_condition, zone_conditions_data): 621 """ 622 Parses the zone conditions definition YAML files to find the condition 623 that match both the zone condition passed in. 624 """ 625 626 condition = {} 627 628 for c in zone_conditions_data['conditions']: 629 630 if zone_condition != c['name']: 631 continue 632 condition['type'] = c['type'] 633 properties = [] 634 for p in c['properties']: 635 property = {} 636 property['property'] = p['property'] 637 property['interface'] = p['interface'] 638 property['path'] = p['path'] 639 property['type'] = p['type'].lower() 640 property['value'] = str(p['value']).lower() 641 properties.append(property) 642 condition['properties'] = properties 643 644 return condition 645 646 647def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data): 648 """ 649 Combines the zone definition YAML and fan 650 definition YAML to create a data structure defining 651 the fan cooling zones. 652 """ 653 654 zone_groups = [] 655 656 for group in zone_data: 657 conditions = [] 658 # zone conditions are optional 659 if 'zone_conditions' in group and group['zone_conditions'] is not None: 660 for c in group['zone_conditions']: 661 662 if not zone_conditions_data: 663 sys.exit("No zone_conditions YAML file but " + 664 "zone_conditions used in zone YAML") 665 666 condition = getConditionInZoneConditions(c['name'], 667 zone_conditions_data) 668 669 if not condition: 670 sys.exit("Missing zone condition " + c['name']) 671 672 conditions.append(condition) 673 674 zone_group = {} 675 zone_group['conditions'] = conditions 676 677 zones = [] 678 for z in group['zones']: 679 zone = {} 680 681 # 'zone' is required 682 if ('zone' not in z) or (z['zone'] is None): 683 sys.exit("Missing fan zone number in " + zone_yaml) 684 685 zone['num'] = z['zone'] 686 687 zone['full_speed'] = z['full_speed'] 688 689 zone['default_floor'] = z['default_floor'] 690 691 # 'increase_delay' is optional (use 0 by default) 692 key = 'increase_delay' 693 zone[key] = z.setdefault(key, 0) 694 695 # 'decrease_interval' is optional (use 0 by default) 696 key = 'decrease_interval' 697 zone[key] = z.setdefault(key, 0) 698 699 # 'cooling_profiles' is optional (use 'all' instead) 700 if ('cooling_profiles' not in z) or \ 701 (z['cooling_profiles'] is None): 702 profiles = ["all"] 703 else: 704 profiles = z['cooling_profiles'] 705 706 fans = getFansInZone(z['zone'], profiles, fan_data) 707 events = getEventsInZone(z['zone'], group['zone_conditions'], 708 events_data) 709 710 if len(fans) == 0: 711 sys.exit("Didn't find any fans in zone " + str(zone['num'])) 712 713 zone['fans'] = fans 714 zone['events'] = events 715 zones.append(zone) 716 717 zone_group['zones'] = zones 718 zone_groups.append(zone_group) 719 720 return zone_groups 721 722 723if __name__ == '__main__': 724 parser = ArgumentParser( 725 description="Phosphor fan zone definition parser") 726 727 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml', 728 default="example/zones.yaml", 729 help='fan zone definitional yaml') 730 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml', 731 default="example/fans.yaml", 732 help='fan definitional yaml') 733 parser.add_argument('-e', '--events_yaml', dest='events_yaml', 734 help='events to set speeds yaml') 735 parser.add_argument('-c', '--zone_conditions_yaml', 736 dest='zone_conditions_yaml', 737 help='conditions to determine zone yaml') 738 parser.add_argument('-o', '--output_dir', dest='output_dir', 739 default=".", 740 help='output directory') 741 args = parser.parse_args() 742 743 if not args.zone_yaml or not args.fan_yaml: 744 parser.print_usage() 745 sys.exit(-1) 746 747 with open(args.zone_yaml, 'r') as zone_input: 748 zone_data = yaml.safe_load(zone_input) or {} 749 750 with open(args.fan_yaml, 'r') as fan_input: 751 fan_data = yaml.safe_load(fan_input) or {} 752 753 events_data = {} 754 if args.events_yaml: 755 with open(args.events_yaml, 'r') as events_input: 756 events_data = yaml.safe_load(events_input) or {} 757 758 zone_conditions_data = {} 759 if args.zone_conditions_yaml: 760 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input: 761 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {} 762 763 zone_config = buildZoneData(zone_data.get('zone_configuration', {}), 764 fan_data, events_data, zone_conditions_data) 765 766 manager_config = zone_data.get('manager_configuration', {}) 767 768 if manager_config.get('power_on_delay') is None: 769 manager_config['power_on_delay'] = 0 770 771 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp") 772 with open(output_file, 'w') as output: 773 output.write(Template(tmpl).render(zones=zone_config, 774 mgr_data=manager_config)) 775