#!/usr/bin/env python """ This script reads in fan definition and zone definition YAML files and generates a set of structures for use by the fan control code. """ import os import sys import yaml from argparse import ArgumentParser from mako.template import Template tmpl = '''\ <%! def indent(str, depth): return ''.join(4*' '*depth+line for line in str.splitlines(True)) %>\ <%def name="genHandler(sig)" buffered="True"> %if ('type' in sig['sparams']) and \ (sig['sparams']['type'] is not None): ${sig['signal']}<${sig['sparams']['type']}>( %else: ${sig['signal']}( %endif %for spk in sig['sparams']['params']: ${sig['sparams'][spk]}, %endfor %if ('type' in sig['hparams']) and \ (sig['hparams']['type'] is not None): handler::${sig['handler']}<${sig['hparams']['type']}>( %else: handler::${sig['handler']}( %endif %for i, hpk in enumerate(sig['hparams']['params']): %if (i+1) != len(sig['hparams']['params']): ${sig['hparams'][hpk]}, %else: ${sig['hparams'][hpk]} %endif %endfor )) \ <%def name="genSSE(event)" buffered="True"> Group{ %for group in event['groups']: %for member in group['members']: { "${member['object']}", {"${member['interface']}", "${member['property']}"} }, %endfor %endfor }, std::vector{ %for a in event['action']: %if len(a['parameters']) != 0: make_action(action::${a['name']}( %else: make_action(action::${a['name']} %endif %for i, p in enumerate(a['parameters']): %if (i+1) != len(a['parameters']): ${p}, %else: ${p}) %endif %endfor ), %endfor }, Timer{ ${event['timer']['interval']}, ${event['timer']['type']} }, std::vector{ %for s in event['signals']: Signal{ match::${s['match']}( %for i, mp in enumerate(s['mparams']): %if (i+1) != len(s['mparams']): "${mp}", %else: "${mp}" %endif %endfor ), make_handler(\ ${indent(genHandler(sig=s), 3)}\ ) }, %endfor } \ /* This is a generated file. */ #include "manager.hpp" #include "functor.hpp" #include "actions.hpp" #include "handlers.hpp" #include "preconditions.hpp" #include "matches.hpp" using namespace phosphor::fan::control; const unsigned int Manager::_powerOnDelay{${mgr_data['power_on_delay']}}; const std::vector Manager::_zoneLayouts { %for zone_group in zones: ZoneGroup{ std::vector{ %for condition in zone_group['conditions']: Condition{ "${condition['type']}", std::vector{ %for property in condition['properties']: ConditionProperty{ "${property['property']}", "${property['interface']}", "${property['path']}", static_cast<${property['type']}>(${property['value']}), }, %endfor }, }, %endfor }, std::vector{ %for zone in zone_group['zones']: ZoneDefinition{ ${zone['num']}, ${zone['full_speed']}, ${zone['default_floor']}, ${zone['increase_delay']}, ${zone['decrease_interval']}, std::vector{ %for fan in zone['fans']: FanDefinition{ "${fan['name']}", std::vector{ %for sensor in fan['sensors']: "${sensor}", %endfor }, "${fan['target_interface']}" }, %endfor }, std::vector{ %for event in zone['events']: %if ('pc' in event) and \ (event['pc'] is not None): SetSpeedEvent{ Group{ %for group in event['pc']['pcgrps']: %for member in group['members']: { "${member['object']}", {"${member['interface']}", "${member['property']}"} }, %endfor %endfor }, std::vector{ %for i, a in enumerate(event['pc']['pcact']): %if len(a['params']) != 0: make_action( precondition::${a['name']}( %else: make_action( precondition::${a['name']} %endif %for p in a['params']: ${p['type']}${p['open']} %for j, v in enumerate(p['values']): %if (j+1) != len(p['values']): ${v['value']}, %else: ${v['value']} %endif %endfor ${p['close']}, %endfor %if (i+1) != len(event['pc']['pcact']): %if len(a['params']) != 0: )), %else: ), %endif %endif %endfor std::vector{ %for pcevt in event['pc']['pcevts']: SetSpeedEvent{\ ${indent(genSSE(event=pcevt), 6)}\ }, %endfor %else: SetSpeedEvent{\ ${indent(genSSE(event=event), 6)} %endif %if ('pc' in event) and (event['pc'] is not None): } %if len(event['pc']['pcact'][-1]['params']) != 0: )), %else: ), %endif }, Timer{ ${event['pc']['pctime']['interval']}, ${event['pc']['pctime']['type']} }, std::vector{ %for s in event['pc']['pcsigs']: Signal{ match::${s['match']}( %for i, mp in enumerate(s['mparams']): %if (i+1) != len(s['mparams']): "${mp}", %else: "${mp}" %endif %endfor ), make_handler(\ ${indent(genHandler(sig=s), 9)}\ ) }, %endfor } %endif }, %endfor } }, %endfor } }, %endfor }; ''' def convertToMap(listOfDict): """ Converts a list of dictionary entries to a std::map initialization list. """ listOfDict = listOfDict.replace('\'', '\"') listOfDict = listOfDict.replace('[', '{') listOfDict = listOfDict.replace(']', '}') listOfDict = listOfDict.replace(':', ',') return listOfDict def getGroups(zNum, zCond, edata, events): """ Extract and construct the groups for the given event. """ groups = [] for eGroups in edata['groups']: if ('zone_conditions' in eGroups) and \ (eGroups['zone_conditions'] is not None): # Zone conditions are optional in the events yaml but skip # if this event's condition is not in this zone's conditions if all('name' in z and z['name'] is not None and not any(c['name'] == z['name'] for c in zCond) for z in eGroups['zone_conditions']): continue # Zone numbers are optional in the events yaml but skip if this # zone's zone number is not in the event's zone numbers if all('zones' in z and z['zones'] is not None and zNum not in z['zones'] for z in eGroups['zone_conditions']): continue eGroup = next(g for g in events['groups'] if g['name'] == eGroups['name']) group = {} members = [] group['name'] = eGroup['name'] for m in eGroup['members']: member = {} member['path'] = eGroup['type'] member['object'] = (eGroup['type'] + m) member['interface'] = eGroups['interface'] member['property'] = eGroups['property']['name'] member['type'] = eGroups['property']['type'] # Add expected group member's property value if given if ('value' in eGroups['property']) and \ (eGroups['property']['value'] is not None): if isinstance(eGroups['property']['value'], str) or \ "string" in str(member['type']).lower(): member['value'] = ( "\"" + eGroups['property']['value'] + "\"") else: member['value'] = eGroups['property']['value'] members.append(member) group['members'] = members groups.append(group) return groups def getActions(edata, actions, events): """ Extracts and constructs the make_action function call for all the actions within the given event. """ action = [] for eActions in actions['actions']: actions = {} eAction = next(a for a in events['actions'] if a['name'] == eActions['name']) actions['name'] = eAction['name'] params = [] if ('parameters' in eAction) and \ (eAction['parameters'] is not None): for p in eAction['parameters']: param = "static_cast<" if type(eActions[p]) is not dict: if p == 'actions': param = "std::vector{" pActs = getActions(edata, eActions, events) for a in pActs: if (len(a['parameters']) != 0): param += ( "make_action(action::" + a['name'] + "(\n") for i, ap in enumerate(a['parameters']): if (i+1) != len(a['parameters']): param += (ap + ",") else: param += (ap + ")") else: param += ("make_action(action::" + a['name']) param += ")," param += "}" elif p == 'property': if isinstance(eActions[p], str) or \ "string" in str(eActions[p]['type']).lower(): param += ( str(eActions[p]['type']).lower() + ">(\"" + str(eActions[p]) + "\")") else: param += ( str(eActions[p]['type']).lower() + ">(" + str(eActions[p]['value']).lower() + ")") else: # Default type to 'size_t' when not given param += ("size_t>(" + str(eActions[p]).lower() + ")") else: if p == 'timer': param = ( "Timer{static_cast(" + str(eActions[p]['delay']) + "), " + "util::Timer::TimerType::" + str(eActions[p]['type']) + "}") else: param += (str(eActions[p]['type']).lower() + ">(") if p != 'map': if isinstance(eActions[p]['value'], str) or \ "string" in str(eActions[p]['type']).lower(): param += \ "\"" + str(eActions[p]['value']) + "\")" else: param += \ str(eActions[p]['value']).lower() + ")" else: param += ( str(eActions[p]['type']).lower() + convertToMap(str(eActions[p]['value'])) + ")") params.append(param) actions['parameters'] = params action.append(actions) return action def getEvent(zone_num, zone_conditions, e, events_data): """ Parses the sections of an event and populates the properties that construct an event within the generated source. """ event = {} # Add set speed event groups grps = getGroups(zone_num, zone_conditions, e, events_data) if not grps: return event['groups'] = grps # Add optional set speed actions and function parameters event['action'] = [] if ('actions' in e) and \ (e['actions'] is not None): event['action'] = getActions(e, e, events_data) # Add signal handlers signals = [] for group in event['groups']: for member in group['members']: for eMatches in e['matches']: signal = {} eMatch = next(m for m in events_data['matches'] if m['name'] == eMatches['name']) signal['match'] = eMatch['name'] params = [] if ('parameters' in eMatch) and \ (eMatch['parameters'] is not None): for p in eMatch['parameters']: params.append(member[str(p)]) signal['mparams'] = params if ('parameters' in eMatch['signal']) and \ (eMatch['signal']['parameters'] is not None): eSignal = eMatch['signal'] else: eSignal = next(s for s in events_data['signals'] if s['name'] == eMatch['signal']) signal['signal'] = eSignal['name'] sparams = {} if ('parameters' in eSignal) and \ (eSignal['parameters'] is not None): splist = [] for p in eSignal['parameters']: sp = str(p) if (sp != 'type'): splist.append(sp) if (sp != 'group'): sparams[sp] = "\"" + member[sp] + "\"" else: sparams[sp] = "Group{\n" for m in group['members']: sparams[sp] += ( "{\n" + "\"" + str(m['object']) + "\",\n" + "{\"" + str(m['interface']) + "\"," + "\"" + str(m['property']) + "\"}\n" + "},\n") sparams[sp] += "}" else: sparams[sp] = member[sp] sparams['params'] = splist signal['sparams'] = sparams # Add signal handler eHandler = next(h for h in events_data['handlers'] if h['name'] == eSignal['handler']) signal['handler'] = eHandler['name'] hparams = {} if ('parameters' in eHandler) and \ (eHandler['parameters'] is not None): hplist = [] for p in eHandler['parameters']: hp = str(p) if (hp != 'type'): hplist.append(hp) if (hp != 'group'): hparams[hp] = "\"" + member[hp] + "\"" else: hparams[hp] = "Group{\n" for m in group['members']: hparams[hp] += ( "{\n" + "\"" + str(m['object']) + "\",\n" + "{\"" + str(m['interface']) + "\"," + "\"" + str(m['property']) + "\"}\n" + "},\n") hparams[hp] += "}" else: hparams[hp] = member[hp] hparams['params'] = hplist signal['hparams'] = hparams signals.append(signal) event['signals'] = signals # Add optional action call timer timer = {} interval = "static_cast" if ('timer' in e) and \ (e['timer'] is not None): timer['interval'] = (interval + "(" + str(e['timer']['interval']) + ")") else: timer['interval'] = (interval + "(" + str(0) + ")") timer['type'] = "util::Timer::TimerType::repeating" event['timer'] = timer return event def addPrecondition(zNum, zCond, event, events_data): """ Parses the precondition section of an event and populates the necessary structures to generate a precondition for a set speed event. """ precond = {} # Add set speed event precondition group grps = getGroups(zNum, zCond, event['precondition'], events_data) if not grps: return precond['pcgrps'] = grps # Add set speed event precondition actions pc = [] pcs = {} pcs['name'] = event['precondition']['name'] epc = next(p for p in events_data['preconditions'] if p['name'] == event['precondition']['name']) params = [] for p in epc['parameters']: param = {} if p == 'groups': param['type'] = "std::vector" param['open'] = "{" param['close'] = "}" values = [] for group in precond['pcgrps']: for pcgrp in group['members']: value = {} value['value'] = ( "PrecondGroup{\"" + str(pcgrp['object']) + "\",\"" + str(pcgrp['interface']) + "\",\"" + str(pcgrp['property']) + "\"," + "static_cast<" + str(pcgrp['type']).lower() + ">") if isinstance(pcgrp['value'], str) or \ "string" in str(pcgrp['type']).lower(): value['value'] += ("(" + str(pcgrp['value']) + ")}") else: value['value'] += \ ("(" + str(pcgrp['value']).lower() + ")}") values.append(value) param['values'] = values params.append(param) pcs['params'] = params pc.append(pcs) precond['pcact'] = pc pcevents = [] for pce in event['precondition']['events']: pcevent = getEvent(zNum, zCond, pce, events_data) if not pcevent: continue pcevents.append(pcevent) precond['pcevts'] = pcevents # Add precondition signal handlers signals = [] for group in precond['pcgrps']: for member in group['members']: for eMatches in event['precondition']['matches']: signal = {} eMatch = next(m for m in events_data['matches'] if m['name'] == eMatches['name']) signal['match'] = eMatch['name'] params = [] if ('parameters' in eMatch) and \ (eMatch['parameters'] is not None): for p in eMatch['parameters']: params.append(member[str(p)]) signal['mparams'] = params if ('parameters' in eMatch['signal']) and \ (eMatch['signal']['parameters'] is not None): eSignal = eMatch['signal'] else: eSignal = next(s for s in events_data['signals'] if s['name'] == eMatch['signal']) signal['signal'] = eSignal['name'] sparams = {} if ('parameters' in eSignal) and \ (eSignal['parameters'] is not None): splist = [] for p in eSignal['parameters']: sp = str(p) if (sp != 'type'): splist.append(sp) if (sp != 'group'): sparams[sp] = "\"" + member[sp] + "\"" else: sparams[sp] = "Group{\n" for m in group: sparams[sp] += ( "{\n" + "\"" + str(m['object']) + "\",\n" + "{\"" + str(m['interface']) + "\"," + "\"" + str(m['property']) + "\"}\n" + "},\n") sparams[sp] += "}" else: sparams[sp] = member[sp] sparams['params'] = splist signal['sparams'] = sparams # Add signal handler eHandler = next(h for h in events_data['handlers'] if h['name'] == eSignal['handler']) signal['handler'] = eHandler['name'] hparams = {} if ('parameters' in eHandler) and \ (eHandler['parameters'] is not None): hplist = [] for p in eHandler['parameters']: hp = str(p) if (hp != 'type'): hplist.append(hp) if (hp != 'group'): hparams[hp] = "\"" + member[hp] + "\"" else: hparams[hp] = "Group{\n" for m in group: hparams[hp] += ( "{\n" + "\"" + str(m['object']) + "\",\n" + "{\"" + str(m['interface']) + "\"," + "\"" + str(m['property']) + "\"}\n" + "},\n") hparams[hp] += "}" else: hparams[hp] = member[hp] hparams['params'] = hplist signal['hparams'] = hparams signals.append(signal) precond['pcsigs'] = signals # Add optional action call timer timer = {} interval = "static_cast" if ('timer' in event['precondition']) and \ (event['precondition']['timer'] is not None): timer['interval'] = (interval + "(" + str(event['precondition']['timer']['interval']) + ")") else: timer['interval'] = (interval + "(" + str(0) + ")") timer['type'] = "util::Timer::TimerType::repeating" precond['pctime'] = timer return precond def getEventsInZone(zone_num, zone_conditions, events_data): """ Constructs the event entries defined for each zone using the events yaml provided. """ events = [] if 'events' in events_data: for e in events_data['events']: event = {} # Add precondition if given if ('precondition' in e) and \ (e['precondition'] is not None): precondition_applies_to_zone = True if 'events' in e['precondition']: for p in e['precondition']['events']: # Verify precondition applies to current zone if 'groups' in p: for g in p['groups']: if 'zone_conditions' in g: zone_allowed = False for zc in g['zone_conditions']: if 'zones' in zc: for z in zc['zones']: if z == zone_num: zone_allowed = True if zone_allowed == False: precondition_applies_to_zone = False if precondition_applies_to_zone == True: event['pc'] = addPrecondition(zone_num, zone_conditions, e, events_data) else: continue else: event = getEvent(zone_num, zone_conditions, e, events_data) if not event: continue events.append(event) return events def getFansInZone(zone_num, profiles, fan_data): """ Parses the fan definition YAML files to find the fans that match both the zone passed in and one of the cooling profiles. """ fans = [] for f in fan_data['fans']: if zone_num != f['cooling_zone']: continue # 'cooling_profile' is optional (use 'all' instead) if f.get('cooling_profile') is None: profile = "all" else: profile = f['cooling_profile'] if profile not in profiles: continue fan = {} fan['name'] = f['inventory'] fan['sensors'] = f['sensors'] fan['target_interface'] = f.get( 'target_interface', 'xyz.openbmc_project.Control.FanSpeed') fans.append(fan) return fans def getConditionInZoneConditions(zone_condition, zone_conditions_data): """ Parses the zone conditions definition YAML files to find the condition that match both the zone condition passed in. """ condition = {} for c in zone_conditions_data['conditions']: if zone_condition != c['name']: continue condition['type'] = c['type'] properties = [] for p in c['properties']: property = {} property['property'] = p['property'] property['interface'] = p['interface'] property['path'] = p['path'] property['type'] = p['type'].lower() property['value'] = str(p['value']).lower() properties.append(property) condition['properties'] = properties return condition def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data): """ Combines the zone definition YAML and fan definition YAML to create a data structure defining the fan cooling zones. """ zone_groups = [] for group in zone_data: conditions = [] # zone conditions are optional if 'zone_conditions' in group and group['zone_conditions'] is not None: for c in group['zone_conditions']: if not zone_conditions_data: sys.exit("No zone_conditions YAML file but " + "zone_conditions used in zone YAML") condition = getConditionInZoneConditions(c['name'], zone_conditions_data) if not condition: sys.exit("Missing zone condition " + c['name']) conditions.append(condition) zone_group = {} zone_group['conditions'] = conditions zones = [] for z in group['zones']: zone = {} # 'zone' is required if ('zone' not in z) or (z['zone'] is None): sys.exit("Missing fan zone number in " + zone_yaml) zone['num'] = z['zone'] zone['full_speed'] = z['full_speed'] zone['default_floor'] = z['default_floor'] # 'increase_delay' is optional (use 0 by default) key = 'increase_delay' zone[key] = z.setdefault(key, 0) # 'decrease_interval' is optional (use 0 by default) key = 'decrease_interval' zone[key] = z.setdefault(key, 0) # 'cooling_profiles' is optional (use 'all' instead) if ('cooling_profiles' not in z) or \ (z['cooling_profiles'] is None): profiles = ["all"] else: profiles = z['cooling_profiles'] fans = getFansInZone(z['zone'], profiles, fan_data) events = getEventsInZone(z['zone'], group['zone_conditions'], events_data) if len(fans) == 0: sys.exit("Didn't find any fans in zone " + str(zone['num'])) zone['fans'] = fans zone['events'] = events zones.append(zone) zone_group['zones'] = zones zone_groups.append(zone_group) return zone_groups if __name__ == '__main__': parser = ArgumentParser( description="Phosphor fan zone definition parser") parser.add_argument('-z', '--zone_yaml', dest='zone_yaml', default="example/zones.yaml", help='fan zone definitional yaml') parser.add_argument('-f', '--fan_yaml', dest='fan_yaml', default="example/fans.yaml", help='fan definitional yaml') parser.add_argument('-e', '--events_yaml', dest='events_yaml', help='events to set speeds yaml') parser.add_argument('-c', '--zone_conditions_yaml', dest='zone_conditions_yaml', help='conditions to determine zone yaml') parser.add_argument('-o', '--output_dir', dest='output_dir', default=".", help='output directory') args = parser.parse_args() if not args.zone_yaml or not args.fan_yaml: parser.print_usage() sys.exit(-1) with open(args.zone_yaml, 'r') as zone_input: zone_data = yaml.safe_load(zone_input) or {} with open(args.fan_yaml, 'r') as fan_input: fan_data = yaml.safe_load(fan_input) or {} events_data = {} if args.events_yaml: with open(args.events_yaml, 'r') as events_input: events_data = yaml.safe_load(events_input) or {} zone_conditions_data = {} if args.zone_conditions_yaml: with open(args.zone_conditions_yaml, 'r') as zone_conditions_input: zone_conditions_data = yaml.safe_load(zone_conditions_input) or {} zone_config = buildZoneData(zone_data.get('zone_configuration', {}), fan_data, events_data, zone_conditions_data) manager_config = zone_data.get('manager_configuration', {}) if manager_config.get('power_on_delay') is None: manager_config['power_on_delay'] = 0 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp") with open(output_file, 'w') as output: output.write(Template(tmpl).render(zones=zone_config, mgr_data=manager_config))