1 #!/usr/bin/env python 2 3 """ 4 This script reads in fan definition and zone definition YAML 5 files and generates a set of structures for use by the fan control code. 6 """ 7 8 import os 9 import sys 10 import yaml 11 from argparse import ArgumentParser 12 from mako.template import Template 13 14 tmpl = ''' 15 <%! 16 def indent(str, depth): 17 return ''.join(4*' '*depth+line for line in str.splitlines(True)) 18 %> 19 <%def name="genSSE(event)" buffered="True"> 20 Group{ 21 %for member in event['group']: 22 { 23 "${member['name']}", 24 {"${member['interface']}", 25 "${member['property']}"} 26 }, 27 %endfor 28 }, 29 std::vector<Action>{ 30 %for a in event['action']: 31 %if len(a['parameters']) != 0: 32 make_action(action::${a['name']}( 33 %else: 34 make_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 }, 46 Timer{ 47 ${event['timer']['interval']} 48 }, 49 std::vector<PropertyChange>{ 50 %for s in event['signal']: 51 PropertyChange{ 52 interfacesAdded("${s['obj_path']}"), 53 make_handler(objectSignal<${s['type']}>( 54 "${s['path']}", 55 "${s['interface']}", 56 "${s['property']}", 57 handler::setProperty<${s['type']}>( 58 "${s['path']}", 59 "${s['interface']}", 60 "${s['property']}" 61 ) 62 )) 63 }, 64 PropertyChange{ 65 propertiesChanged( 66 "${s['path']}", 67 "${s['interface']}"), 68 make_handler(propertySignal<${s['type']}>( 69 "${s['interface']}", 70 "${s['property']}", 71 handler::setProperty<${s['type']}>( 72 "${s['path']}", 73 "${s['interface']}", 74 "${s['property']}" 75 ) 76 )) 77 }, 78 %endfor 79 } 80 </%def> 81 /* This is a generated file. */ 82 #include <sdbusplus/bus.hpp> 83 #include "manager.hpp" 84 #include "functor.hpp" 85 #include "actions.hpp" 86 #include "handlers.hpp" 87 #include "preconditions.hpp" 88 89 using namespace phosphor::fan::control; 90 using namespace sdbusplus::bus::match::rules; 91 92 const unsigned int Manager::_powerOnDelay{${mgr_data['power_on_delay']}}; 93 94 const std::vector<ZoneGroup> Manager::_zoneLayouts 95 { 96 %for zone_group in zones: 97 ZoneGroup{ 98 std::vector<Condition>{ 99 %for condition in zone_group['conditions']: 100 Condition{ 101 "${condition['type']}", 102 std::vector<ConditionProperty>{ 103 %for property in condition['properties']: 104 ConditionProperty{ 105 "${property['property']}", 106 "${property['interface']}", 107 "${property['path']}", 108 static_cast<${property['type']}>(${property['value']}), 109 }, 110 %endfor 111 }, 112 }, 113 %endfor 114 }, 115 std::vector<ZoneDefinition>{ 116 %for zone in zone_group['zones']: 117 ZoneDefinition{ 118 ${zone['num']}, 119 ${zone['full_speed']}, 120 ${zone['default_floor']}, 121 ${zone['increase_delay']}, 122 ${zone['decrease_interval']}, 123 std::vector<FanDefinition>{ 124 %for fan in zone['fans']: 125 FanDefinition{ 126 "${fan['name']}", 127 std::vector<std::string>{ 128 %for sensor in fan['sensors']: 129 "${sensor}", 130 %endfor 131 } 132 }, 133 %endfor 134 }, 135 std::vector<SetSpeedEvent>{ 136 %for event in zone['events']: 137 %if ('pc' in event) and \ 138 (event['pc'] is not None): 139 SetSpeedEvent{ 140 Group{ 141 %for member in event['pc']['pcgrp']: 142 { 143 "${member['name']}", 144 {"${member['interface']}", 145 "${member['property']}"} 146 }, 147 %endfor 148 }, 149 std::vector<Action>{ 150 %for i, a in enumerate(event['pc']['pcact']): 151 %if len(a['params']) != 0: 152 make_action( 153 precondition::${a['name']}( 154 %else: 155 make_action( 156 precondition::${a['name']} 157 %endif 158 %for p in a['params']: 159 ${p['type']}${p['open']} 160 %for j, v in enumerate(p['values']): 161 %if (j+1) != len(p['values']): 162 ${v['value']}, 163 %else: 164 ${v['value']} 165 %endif 166 %endfor 167 ${p['close']}, 168 %endfor 169 %if (i+1) != len(event['pc']['pcact']): 170 %if len(a['params']) != 0: 171 )), 172 %else: 173 ), 174 %endif 175 %endif 176 %endfor 177 std::vector<SetSpeedEvent>{ 178 %for pcevt in event['pc']['pcevts']: 179 SetSpeedEvent{\ 180 ${indent(genSSE(event=pcevt), 6)}\ 181 }, 182 %endfor 183 %else: 184 SetSpeedEvent{\ 185 ${indent(genSSE(event=event), 6)} 186 %endif 187 %if ('pc' in event) and (event['pc'] is not None): 188 } 189 %if len(event['pc']['pcact'][-1]['params']) != 0: 190 )), 191 %else: 192 ), 193 %endif 194 }, 195 Timer{ 196 ${event['pc']['pctime']['interval']} 197 }, 198 std::vector<PropertyChange>{ 199 %for s in event['pc']['pcsig']: 200 PropertyChange{ 201 interfacesAdded("${s['obj_path']}"), 202 make_handler(objectSignal<${s['type']}>( 203 "${s['path']}", 204 "${s['interface']}", 205 "${s['property']}", 206 handler::setProperty<${s['type']}>( 207 "${s['path']}", 208 "${s['interface']}", 209 "${s['property']}" 210 ) 211 )) 212 }, 213 PropertyChange{ 214 propertiesChanged( 215 "${s['path']}", 216 "${s['interface']}"), 217 make_handler(propertySignal<${s['type']}>( 218 "${s['interface']}", 219 "${s['property']}", 220 handler::setProperty<${s['type']}>( 221 "${s['path']}", 222 "${s['interface']}", 223 "${s['property']}" 224 ) 225 )) 226 }, 227 %endfor 228 } 229 %endif 230 }, 231 %endfor 232 } 233 }, 234 %endfor 235 } 236 }, 237 %endfor 238 }; 239 ''' 240 241 242 def 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 252 def 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['obj_path'] = groups['type'] 280 members['name'] = (groups['type'] + 281 member) 282 members['interface'] = e['interface'] 283 members['property'] = e['property']['name'] 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 property change signal handler 323 signal = [] 324 for path in group: 325 signals = {} 326 signals['obj_path'] = path['obj_path'] 327 signals['path'] = path['name'] 328 signals['interface'] = e['interface'] 329 signals['property'] = e['property']['name'] 330 signals['type'] = e['property']['type'] 331 signal.append(signals) 332 event['signal'] = signal 333 334 # Add optional action call timer 335 timer = {} 336 interval = "static_cast<std::chrono::seconds>" 337 if ('timer' in e) and \ 338 (e['timer'] is not None): 339 timer['interval'] = (interval + 340 "(" + 341 str(e['timer']['interval']) + 342 ")") 343 else: 344 timer['interval'] = (interval + 345 "(" + str(0) + ")") 346 event['timer'] = timer 347 348 return event 349 350 351 def addPrecondition(zNum, zCond, event, events_data): 352 """ 353 Parses the precondition section of an event and populates the necessary 354 structures to generate a precondition for a set speed event. 355 """ 356 precond = {} 357 # Add set speed event precondition group 358 group = [] 359 for grp in event['precondition']['groups']: 360 groups = next(g for g in events_data['groups'] 361 if g['name'] == grp['name']) 362 for member in groups['members']: 363 members = {} 364 members['obj_path'] = groups['type'] 365 members['name'] = (groups['type'] + 366 member) 367 members['interface'] = grp['interface'] 368 members['property'] = grp['property']['name'] 369 members['type'] = grp['property']['type'] 370 members['value'] = grp['property']['value'] 371 group.append(members) 372 precond['pcgrp'] = group 373 374 # Add set speed event precondition actions 375 pc = [] 376 pcs = {} 377 pcs['name'] = event['precondition']['name'] 378 epc = next(p for p in events_data['preconditions'] 379 if p['name'] == event['precondition']['name']) 380 params = [] 381 for p in epc['parameters']: 382 param = {} 383 if p == 'groups': 384 param['type'] = "std::vector<PrecondGroup>" 385 param['open'] = "{" 386 param['close'] = "}" 387 values = [] 388 for pcgrp in group: 389 value = {} 390 value['value'] = ( 391 "PrecondGroup{\"" + 392 str(pcgrp['name']) + "\",\"" + 393 str(pcgrp['interface']) + "\",\"" + 394 str(pcgrp['property']) + "\"," + 395 "static_cast<" + 396 str(pcgrp['type']).lower() + ">" + 397 "(" + str(pcgrp['value']).lower() + ")}") 398 values.append(value) 399 param['values'] = values 400 params.append(param) 401 pcs['params'] = params 402 pc.append(pcs) 403 precond['pcact'] = pc 404 405 pcevents = [] 406 for pce in event['precondition']['events']: 407 pcevent = getEvent(zNum, zCond, pce, events_data) 408 if not pcevent: 409 continue 410 pcevents.append(pcevent) 411 precond['pcevts'] = pcevents 412 413 # Add precondition property change signal handler 414 signal = [] 415 for member in group: 416 signals = {} 417 signals['obj_path'] = member['obj_path'] 418 signals['path'] = member['name'] 419 signals['interface'] = member['interface'] 420 signals['property'] = member['property'] 421 signals['type'] = member['type'] 422 signal.append(signals) 423 precond['pcsig'] = signal 424 425 # Add optional action call timer 426 timer = {} 427 interval = "static_cast<std::chrono::seconds>" 428 if ('timer' in event['precondition']) and \ 429 (event['precondition']['timer'] is not None): 430 timer['interval'] = (interval + 431 "(" + 432 str(event['precondition']['timer']['interval']) + 433 ")") 434 else: 435 timer['interval'] = (interval + 436 "(" + str(0) + ")") 437 precond['pctime'] = timer 438 439 return precond 440 441 442 def getEventsInZone(zone_num, zone_conditions, events_data): 443 """ 444 Constructs the event entries defined for each zone using the events yaml 445 provided. 446 """ 447 events = [] 448 449 if 'events' in events_data: 450 for e in events_data['events']: 451 event = {} 452 # Add precondition if given 453 if ('precondition' in e) and \ 454 (e['precondition'] is not None): 455 event['pc'] = addPrecondition(zone_num, 456 zone_conditions, 457 e, 458 events_data) 459 else: 460 event = getEvent(zone_num, zone_conditions, e, events_data) 461 if not event: 462 continue 463 events.append(event) 464 465 return events 466 467 468 def getFansInZone(zone_num, profiles, fan_data): 469 """ 470 Parses the fan definition YAML files to find the fans 471 that match both the zone passed in and one of the 472 cooling profiles. 473 """ 474 475 fans = [] 476 477 for f in fan_data['fans']: 478 479 if zone_num != f['cooling_zone']: 480 continue 481 482 # 'cooling_profile' is optional (use 'all' instead) 483 if f.get('cooling_profile') is None: 484 profile = "all" 485 else: 486 profile = f['cooling_profile'] 487 488 if profile not in profiles: 489 continue 490 491 fan = {} 492 fan['name'] = f['inventory'] 493 fan['sensors'] = f['sensors'] 494 fans.append(fan) 495 496 return fans 497 498 499 def getConditionInZoneConditions(zone_condition, zone_conditions_data): 500 """ 501 Parses the zone conditions definition YAML files to find the condition 502 that match both the zone condition passed in. 503 """ 504 505 condition = {} 506 507 for c in zone_conditions_data['conditions']: 508 509 if zone_condition != c['name']: 510 continue 511 condition['type'] = c['type'] 512 properties = [] 513 for p in c['properties']: 514 property = {} 515 property['property'] = p['property'] 516 property['interface'] = p['interface'] 517 property['path'] = p['path'] 518 property['type'] = p['type'].lower() 519 property['value'] = str(p['value']).lower() 520 properties.append(property) 521 condition['properties'] = properties 522 523 return condition 524 525 526 def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data): 527 """ 528 Combines the zone definition YAML and fan 529 definition YAML to create a data structure defining 530 the fan cooling zones. 531 """ 532 533 zone_groups = [] 534 535 for group in zone_data: 536 conditions = [] 537 # zone conditions are optional 538 if 'zone_conditions' in group and group['zone_conditions'] is not None: 539 for c in group['zone_conditions']: 540 541 if not zone_conditions_data: 542 sys.exit("No zone_conditions YAML file but " + 543 "zone_conditions used in zone YAML") 544 545 condition = getConditionInZoneConditions(c['name'], 546 zone_conditions_data) 547 548 if not condition: 549 sys.exit("Missing zone condition " + c['name']) 550 551 conditions.append(condition) 552 553 zone_group = {} 554 zone_group['conditions'] = conditions 555 556 zones = [] 557 for z in group['zones']: 558 zone = {} 559 560 # 'zone' is required 561 if ('zone' not in z) or (z['zone'] is None): 562 sys.exit("Missing fan zone number in " + zone_yaml) 563 564 zone['num'] = z['zone'] 565 566 zone['full_speed'] = z['full_speed'] 567 568 zone['default_floor'] = z['default_floor'] 569 570 # 'increase_delay' is optional (use 0 by default) 571 key = 'increase_delay' 572 zone[key] = z.setdefault(key, 0) 573 574 # 'decrease_interval' is optional (use 0 by default) 575 key = 'decrease_interval' 576 zone[key] = z.setdefault(key, 0) 577 578 # 'cooling_profiles' is optional (use 'all' instead) 579 if ('cooling_profiles' not in z) or \ 580 (z['cooling_profiles'] is None): 581 profiles = ["all"] 582 else: 583 profiles = z['cooling_profiles'] 584 585 fans = getFansInZone(z['zone'], profiles, fan_data) 586 events = getEventsInZone(z['zone'], group['zone_conditions'], 587 events_data) 588 589 if len(fans) == 0: 590 sys.exit("Didn't find any fans in zone " + str(zone['num'])) 591 592 zone['fans'] = fans 593 zone['events'] = events 594 zones.append(zone) 595 596 zone_group['zones'] = zones 597 zone_groups.append(zone_group) 598 599 return zone_groups 600 601 602 if __name__ == '__main__': 603 parser = ArgumentParser( 604 description="Phosphor fan zone definition parser") 605 606 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml', 607 default="example/zones.yaml", 608 help='fan zone definitional yaml') 609 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml', 610 default="example/fans.yaml", 611 help='fan definitional yaml') 612 parser.add_argument('-e', '--events_yaml', dest='events_yaml', 613 help='events to set speeds yaml') 614 parser.add_argument('-c', '--zone_conditions_yaml', 615 dest='zone_conditions_yaml', 616 help='conditions to determine zone yaml') 617 parser.add_argument('-o', '--output_dir', dest='output_dir', 618 default=".", 619 help='output directory') 620 args = parser.parse_args() 621 622 if not args.zone_yaml or not args.fan_yaml: 623 parser.print_usage() 624 sys.exit(-1) 625 626 with open(args.zone_yaml, 'r') as zone_input: 627 zone_data = yaml.safe_load(zone_input) or {} 628 629 with open(args.fan_yaml, 'r') as fan_input: 630 fan_data = yaml.safe_load(fan_input) or {} 631 632 events_data = {} 633 if args.events_yaml: 634 with open(args.events_yaml, 'r') as events_input: 635 events_data = yaml.safe_load(events_input) or {} 636 637 zone_conditions_data = {} 638 if args.zone_conditions_yaml: 639 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input: 640 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {} 641 642 zone_config = buildZoneData(zone_data.get('zone_configuration', {}), 643 fan_data, events_data, zone_conditions_data) 644 645 manager_config = zone_data.get('manager_configuration', {}) 646 647 if manager_config.get('power_on_delay') is None: 648 manager_config['power_on_delay'] = 0 649 650 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp") 651 with open(output_file, 'w') as output: 652 output.write(Template(tmpl).render(zones=zone_config, 653 mgr_data=manager_config)) 654