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