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