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