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