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