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 std::vector<PropertyChange>{ 116 %for s in event['signal']: 117 PropertyChange{ 118 interfacesAdded("${s['obj_path']}"), 119 make_handler(objectSignal<${s['type']}>( 120 "${s['path']}", 121 "${s['interface']}", 122 "${s['property']}", 123 handler::setProperty<${s['type']}>( 124 "${s['path']}", 125 "${s['interface']}", 126 "${s['property']}" 127 ) 128 )) 129 }, 130 PropertyChange{ 131 propertiesChanged( 132 "${s['path']}", 133 "${s['interface']}"), 134 make_handler(propertySignal<${s['type']}>( 135 "${s['interface']}", 136 "${s['property']}", 137 handler::setProperty<${s['type']}>( 138 "${s['path']}", 139 "${s['interface']}", 140 "${s['property']}" 141 ) 142 )) 143 }, 144 %endfor 145 } 146 %if ('pc' in event) and (event['pc'] is not None): 147 } 148 )), 149 std::vector<PropertyChange>{ 150 %for s in event['pc']['pcsig']: 151 PropertyChange{ 152 interfacesAdded("${s['obj_path']}"), 153 make_handler(objectSignal<${s['type']}>( 154 "${s['path']}", 155 "${s['interface']}", 156 "${s['property']}", 157 handler::setProperty<${s['type']}>( 158 "${s['path']}", 159 "${s['interface']}", 160 "${s['property']}" 161 ) 162 )) 163 }, 164 PropertyChange{ 165 propertiesChanged( 166 "${s['path']}", 167 "${s['interface']}"), 168 make_handler(propertySignal<${s['type']}>( 169 "${s['interface']}", 170 "${s['property']}", 171 handler::setProperty<${s['type']}>( 172 "${s['path']}", 173 "${s['interface']}", 174 "${s['property']}" 175 ) 176 )) 177 }, 178 %endfor 179 } 180 %endif 181 }, 182 %endfor 183 } 184 }, 185 %endfor 186 } 187 }, 188%endfor 189}; 190''' 191 192 193def convertToMap(listOfDict): 194 """ 195 Converts a list of dictionary entries to a std::map initialization list. 196 """ 197 listOfDict = listOfDict.replace('[', '{') 198 listOfDict = listOfDict.replace(']', '}') 199 listOfDict = listOfDict.replace(':', ',') 200 return listOfDict 201 202 203def addPrecondition(event, events_data): 204 """ 205 Parses the precondition section of an event and populates the necessary 206 structures to generate a precondition for a set speed event. 207 """ 208 precond = {} 209 # Add set speed event precondition group 210 group = [] 211 for grp in event['precondition']['groups']: 212 groups = next(g for g in events_data['groups'] 213 if g['name'] == grp['name']) 214 for member in groups['members']: 215 members = {} 216 members['obj_path'] = groups['type'] 217 members['name'] = (groups['type'] + 218 member) 219 members['interface'] = grp['interface'] 220 members['property'] = grp['property']['name'] 221 members['type'] = grp['property']['type'] 222 members['value'] = grp['property']['value'] 223 group.append(members) 224 precond['pcgrp'] = group 225 226 # Add set speed event precondition action 227 pc = {} 228 pc['name'] = event['precondition']['name'] 229 pcs = next(p for p in events_data['preconditions'] 230 if p['name'] == event['precondition']['name']) 231 params = [] 232 for p in pcs['parameters']: 233 param = {} 234 if p == 'groups': 235 param['type'] = "std::vector<PrecondGroup>" 236 param['open'] = "{" 237 param['close'] = "}" 238 values = [] 239 for pcgrp in group: 240 value = {} 241 value['value'] = ( 242 "PrecondGroup{\"" + 243 str(pcgrp['name']) + "\",\"" + 244 str(pcgrp['interface']) + "\",\"" + 245 str(pcgrp['property']) + "\"," + 246 "static_cast<" + 247 str(pcgrp['type']).lower() + ">" + 248 "(" + str(pcgrp['value']).lower() + ")}") 249 values.append(value) 250 param['values'] = values 251 params.append(param) 252 pc['params'] = params 253 precond['pcact'] = pc 254 255 # Add precondition property change signal handler 256 signal = [] 257 for member in group: 258 signals = {} 259 signals['obj_path'] = member['obj_path'] 260 signals['path'] = member['name'] 261 signals['interface'] = member['interface'] 262 signals['property'] = member['property'] 263 signals['type'] = member['type'] 264 signal.append(signals) 265 precond['pcsig'] = signal 266 267 return precond 268 269 270def getEventsInZone(zone_num, zone_conditions, events_data): 271 """ 272 Constructs the event entries defined for each zone using the events yaml 273 provided. 274 """ 275 events = [] 276 277 if 'events' in events_data: 278 for e in events_data['events']: 279 280 # Zone numbers are optional in the events yaml but skip if this 281 # zone's zone number is not in the event's zone numbers 282 if all('zones' in z and z['zones'] is not None and 283 zone_num not in z['zones'] for z in e['zone_conditions']): 284 continue 285 286 # Zone conditions are optional in the events yaml but skip if this 287 # event's condition is not in this zone's conditions 288 if all('name' in z and z['name'] is not None and 289 not any(c['name'] == z['name'] for c in zone_conditions) 290 for z in e['zone_conditions']): 291 continue 292 293 event = {} 294 # Add precondition if given 295 if ('precondition' in e) and \ 296 (e['precondition'] is not None): 297 event['pc'] = addPrecondition(e, events_data) 298 299 # Add set speed event group 300 group = [] 301 groups = next(g for g in events_data['groups'] 302 if g['name'] == e['group']) 303 for member in groups['members']: 304 members = {} 305 members['obj_path'] = groups['type'] 306 members['name'] = (groups['type'] + 307 member) 308 members['interface'] = e['interface'] 309 members['property'] = e['property']['name'] 310 group.append(members) 311 event['group'] = group 312 313 # Add set speed action and function parameters 314 action = {} 315 actions = next(a for a in events_data['actions'] 316 if a['name'] == e['action']['name']) 317 action['name'] = actions['name'] 318 params = [] 319 for p in actions['parameters']: 320 param = {} 321 if type(e['action'][p]) is not dict: 322 if p == 'property': 323 param['value'] = str(e['action'][p]).lower() 324 param['type'] = str(e['property']['type']).lower() 325 else: 326 # Default type to 'size_t' when not given 327 param['value'] = str(e['action'][p]).lower() 328 param['type'] = 'size_t' 329 params.append(param) 330 else: 331 param['type'] = str(e['action'][p]['type']).lower() 332 if p != 'map': 333 param['value'] = str(e['action'][p]['value']).lower() 334 else: 335 emap = convertToMap(str(e['action'][p]['value'])) 336 param['value'] = param['type'] + emap 337 params.append(param) 338 action['parameters'] = params 339 event['action'] = action 340 341 # Add property change signal handler 342 signal = [] 343 for path in group: 344 signals = {} 345 signals['obj_path'] = path['obj_path'] 346 signals['path'] = path['name'] 347 signals['interface'] = e['interface'] 348 signals['property'] = e['property']['name'] 349 signals['type'] = e['property']['type'] 350 signal.append(signals) 351 event['signal'] = signal 352 353 events.append(event) 354 355 return events 356 357 358def getFansInZone(zone_num, profiles, fan_data): 359 """ 360 Parses the fan definition YAML files to find the fans 361 that match both the zone passed in and one of the 362 cooling profiles. 363 """ 364 365 fans = [] 366 367 for f in fan_data['fans']: 368 369 if zone_num != f['cooling_zone']: 370 continue 371 372 # 'cooling_profile' is optional (use 'all' instead) 373 if f.get('cooling_profile') is None: 374 profile = "all" 375 else: 376 profile = f['cooling_profile'] 377 378 if profile not in profiles: 379 continue 380 381 fan = {} 382 fan['name'] = f['inventory'] 383 fan['sensors'] = f['sensors'] 384 fans.append(fan) 385 386 return fans 387 388 389def getConditionInZoneConditions(zone_condition, zone_conditions_data): 390 """ 391 Parses the zone conditions definition YAML files to find the condition 392 that match both the zone condition passed in. 393 """ 394 395 condition = {} 396 397 for c in zone_conditions_data['conditions']: 398 399 if zone_condition != c['name']: 400 continue 401 condition['type'] = c['type'] 402 properties = [] 403 for p in c['properties']: 404 property = {} 405 property['property'] = p['property'] 406 property['interface'] = p['interface'] 407 property['path'] = p['path'] 408 property['type'] = p['type'].lower() 409 property['value'] = str(p['value']).lower() 410 properties.append(property) 411 condition['properties'] = properties 412 413 return condition 414 415 416def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data): 417 """ 418 Combines the zone definition YAML and fan 419 definition YAML to create a data structure defining 420 the fan cooling zones. 421 """ 422 423 zone_groups = [] 424 425 for group in zone_data: 426 conditions = [] 427 # zone conditions are optional 428 if 'zone_conditions' in group and group['zone_conditions'] is not None: 429 for c in group['zone_conditions']: 430 431 if not zone_conditions_data: 432 sys.exit("No zone_conditions YAML file but " + 433 "zone_conditions used in zone YAML") 434 435 condition = getConditionInZoneConditions(c['name'], 436 zone_conditions_data) 437 438 if not condition: 439 sys.exit("Missing zone condition " + c['name']) 440 441 conditions.append(condition) 442 443 zone_group = {} 444 zone_group['conditions'] = conditions 445 446 zones = [] 447 for z in group['zones']: 448 zone = {} 449 450 # 'zone' is required 451 if ('zone' not in z) or (z['zone'] is None): 452 sys.exit("Missing fan zone number in " + zone_yaml) 453 454 zone['num'] = z['zone'] 455 456 zone['full_speed'] = z['full_speed'] 457 458 zone['default_floor'] = z['default_floor'] 459 460 # 'increase_delay' is optional (use 0 by default) 461 key = 'increase_delay' 462 zone[key] = z.setdefault(key, 0) 463 464 # 'decrease_interval' is optional (use 0 by default) 465 key = 'decrease_interval' 466 zone[key] = z.setdefault(key, 0) 467 468 # 'cooling_profiles' is optional (use 'all' instead) 469 if ('cooling_profiles' not in z) or \ 470 (z['cooling_profiles'] is None): 471 profiles = ["all"] 472 else: 473 profiles = z['cooling_profiles'] 474 475 fans = getFansInZone(z['zone'], profiles, fan_data) 476 events = getEventsInZone(z['zone'], group['zone_conditions'], 477 events_data) 478 479 if len(fans) == 0: 480 sys.exit("Didn't find any fans in zone " + str(zone['num'])) 481 482 zone['fans'] = fans 483 zone['events'] = events 484 zones.append(zone) 485 486 zone_group['zones'] = zones 487 zone_groups.append(zone_group) 488 489 return zone_groups 490 491 492if __name__ == '__main__': 493 parser = ArgumentParser( 494 description="Phosphor fan zone definition parser") 495 496 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml', 497 default="example/zones.yaml", 498 help='fan zone definitional yaml') 499 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml', 500 default="example/fans.yaml", 501 help='fan definitional yaml') 502 parser.add_argument('-e', '--events_yaml', dest='events_yaml', 503 help='events to set speeds yaml') 504 parser.add_argument('-c', '--zone_conditions_yaml', 505 dest='zone_conditions_yaml', 506 help='conditions to determine zone yaml') 507 parser.add_argument('-o', '--output_dir', dest='output_dir', 508 default=".", 509 help='output directory') 510 args = parser.parse_args() 511 512 if not args.zone_yaml or not args.fan_yaml: 513 parser.print_usage() 514 sys.exit(-1) 515 516 with open(args.zone_yaml, 'r') as zone_input: 517 zone_data = yaml.safe_load(zone_input) or {} 518 519 with open(args.fan_yaml, 'r') as fan_input: 520 fan_data = yaml.safe_load(fan_input) or {} 521 522 events_data = {} 523 if args.events_yaml: 524 with open(args.events_yaml, 'r') as events_input: 525 events_data = yaml.safe_load(events_input) or {} 526 527 zone_conditions_data = {} 528 if args.zone_conditions_yaml: 529 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input: 530 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {} 531 532 zone_config = buildZoneData(zone_data.get('zone_configuration', {}), 533 fan_data, events_data, zone_conditions_data) 534 535 manager_config = zone_data.get('manager_configuration', {}) 536 537 if manager_config.get('power_on_delay') is None: 538 manager_config['power_on_delay'] = 0 539 540 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp") 541 with open(output_file, 'w') as output: 542 output.write(Template(tmpl).render(zones=zone_config, 543 mgr_data=manager_config)) 544