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