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