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