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