blob: 177bfe0092c0f85619c4e198b57265bf641df5a0 [file] [log] [blame]
Matt Spinlerd08dbe22017-04-11 13:52:54 -05001#!/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
Matt Spinlerd08dbe22017-04-11 13:52:54 -050014tmpl = '''/* This is a generated file. */
Matthew Barth34f1bda2017-05-31 13:45:36 -050015#include <sdbusplus/bus.hpp>
Matt Spinlerd08dbe22017-04-11 13:52:54 -050016#include "manager.hpp"
Matthew Barthba102b32017-05-16 16:13:56 -050017#include "functor.hpp"
18#include "actions.hpp"
Matthew Barth7e527c12017-05-17 10:14:15 -050019#include "handlers.hpp"
Matt Spinlerd08dbe22017-04-11 13:52:54 -050020
21using namespace phosphor::fan::control;
Matthew Barth34f1bda2017-05-31 13:45:36 -050022using namespace sdbusplus::bus::match::rules;
Matt Spinlerd08dbe22017-04-11 13:52:54 -050023
Matt Spinleree7f6422017-05-09 11:03:14 -050024const unsigned int Manager::_powerOnDelay{${mgr_data['power_on_delay']}};
25
Matt Spinlerd08dbe22017-04-11 13:52:54 -050026const std::vector<ZoneGroup> Manager::_zoneLayouts
27{
28%for zone_group in zones:
Matthew Barth9c726672017-05-18 13:44:46 -050029 ZoneGroup{
Gunnar Millsee8a2812017-06-02 14:26:47 -050030 std::vector<Condition>{
31 %for condition in zone_group['conditions']:
32 Condition{
33 "${condition['type']}",
34 std::vector<ConditionProperty>{
35 %for property in condition['properties']:
36 ConditionProperty{
37 "${property['property']}",
38 "${property['interface']}",
39 "${property['path']}",
40 static_cast<${property['type']}>(${property['value']}),
41 },
42 %endfor
43 },
44 },
45 %endfor
46 },
Matthew Barth9c726672017-05-18 13:44:46 -050047 std::vector<ZoneDefinition>{
48 %for zone in zone_group['zones']:
49 ZoneDefinition{
50 ${zone['num']},
51 ${zone['full_speed']},
Matthew Barth1de66622017-06-12 13:13:02 -050052 ${zone['default_floor']},
Matthew Bartha9561842017-06-29 11:43:45 -050053 ${zone['increase_delay']},
54 ${zone['decrease_interval']},
Matthew Barth9c726672017-05-18 13:44:46 -050055 std::vector<FanDefinition>{
56 %for fan in zone['fans']:
57 FanDefinition{
58 "${fan['name']}",
59 std::vector<std::string>{
60 %for sensor in fan['sensors']:
61 "${sensor}",
62 %endfor
63 }
64 },
65 %endfor
66 },
67 std::vector<SetSpeedEvent>{
68 %for event in zone['events']:
69 SetSpeedEvent{
70 Group{
71 %for member in event['group']:
72 {
73 "${member['name']}",
74 {"${member['interface']}",
75 "${member['property']}"}
76 },
77 %endfor
78 },
79 make_action(action::${event['action']['name']}(
80 %for i, p in enumerate(event['action']['parameters']):
81 %if (i+1) != len(event['action']['parameters']):
82 static_cast<${p['type']}>(${p['value']}),
83 %else:
84 static_cast<${p['type']}>(${p['value']})
85 %endif
86 %endfor
87 )),
88 std::vector<PropertyChange>{
89 %for s in event['signal']:
90 PropertyChange{
Matthew Barth34f1bda2017-05-31 13:45:36 -050091 interface("org.freedesktop.DBus.Properties") +
92 member("PropertiesChanged") +
93 type::signal() +
94 path("${s['path']}") +
95 arg0namespace("${s['interface']}"),
Matthew Barth9c726672017-05-18 13:44:46 -050096 make_handler(propertySignal<${s['type']}>(
97 "${s['interface']}",
98 "${s['property']}",
99 handler::setProperty<${s['type']}>(
100 "${s['member']}",
Matthew Barthcec5ab72017-06-02 15:20:56 -0500101 "${s['interface']}",
Matthew Barth9c726672017-05-18 13:44:46 -0500102 "${s['property']}"
103 )
104 ))
105 },
106 %endfor
107 }
108 },
109 %endfor
110 }
111 },
112 %endfor
113 }
Matt Spinler78498c92017-04-11 13:59:46 -0500114 },
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500115%endfor
116};
117'''
118
Matt Spinler78498c92017-04-11 13:59:46 -0500119
Matthew Barthbb12c922017-06-13 13:57:40 -0500120def convertToMap(listOfDict):
121 """
122 Converts a list of dictionary entries to a std::map initialization list.
123 """
124 listOfDict = listOfDict.replace('[', '{')
125 listOfDict = listOfDict.replace(']', '}')
126 listOfDict = listOfDict.replace(':', ',')
127 return listOfDict
128
129
Gunnar Millsb751f322017-06-06 15:14:11 -0500130def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500131 """
132 Constructs the event entries defined for each zone using the events yaml
133 provided.
134 """
135 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500136
Matthew Barthd4d0f082017-05-16 13:51:10 -0500137 if 'events' in events_data:
138 for e in events_data['events']:
Gunnar Millsb751f322017-06-06 15:14:11 -0500139
140 # Zone numbers are optional in the events yaml but skip if this
141 # zone's zone number is not in the event's zone numbers
142 if all('zones' in z and z['zones'] is not None and
143 zone_num not in z['zones'] for z in e['zone_conditions']):
144 continue
145
146 # Zone conditions are optional in the events yaml but skip if this
147 # event's condition is not in this zone's conditions
148 if all('name' in z and z['name'] is not None and
149 not any(c['name'] == z['name'] for c in zone_conditions)
150 for z in e['zone_conditions']):
151 continue
Matthew Barthd4d0f082017-05-16 13:51:10 -0500152
153 event = {}
154 # Add set speed event group
155 group = []
156 groups = next(g for g in events_data['groups']
157 if g['name'] == e['group'])
158 for member in groups['members']:
159 members = {}
Matthew Barth7e527c12017-05-17 10:14:15 -0500160 members['type'] = groups['type']
Matthew Barthd4d0f082017-05-16 13:51:10 -0500161 members['name'] = ("/xyz/openbmc_project/" +
162 groups['type'] +
163 member)
164 members['interface'] = e['interface']
165 members['property'] = e['property']['name']
166 group.append(members)
167 event['group'] = group
Matthew Barthba102b32017-05-16 16:13:56 -0500168
169 # Add set speed action and function parameters
170 action = {}
171 actions = next(a for a in events_data['actions']
Matthew Barth9c726672017-05-18 13:44:46 -0500172 if a['name'] == e['action']['name'])
Matthew Barthba102b32017-05-16 16:13:56 -0500173 action['name'] = actions['name']
174 params = []
175 for p in actions['parameters']:
176 param = {}
177 if type(e['action'][p]) is not dict:
178 if p == 'property':
179 param['value'] = str(e['action'][p]).lower()
180 param['type'] = str(e['property']['type']).lower()
181 else:
182 # Default type to 'size_t' when not given
183 param['value'] = str(e['action'][p]).lower()
184 param['type'] = 'size_t'
185 params.append(param)
186 else:
Matthew Barthba102b32017-05-16 16:13:56 -0500187 param['type'] = str(e['action'][p]['type']).lower()
Matthew Barthbb12c922017-06-13 13:57:40 -0500188 if p != 'map':
189 param['value'] = str(e['action'][p]['value']).lower()
190 else:
191 emap = convertToMap(str(e['action'][p]['value']))
192 param['value'] = param['type'] + emap
Matthew Barthba102b32017-05-16 16:13:56 -0500193 params.append(param)
194 action['parameters'] = params
195 event['action'] = action
196
Matthew Barth7e527c12017-05-17 10:14:15 -0500197 # Add property change signal handler
198 signal = []
199 for path in group:
200 signals = {}
201 signals['path'] = path['name']
202 signals['interface'] = e['interface']
203 signals['property'] = e['property']['name']
204 signals['type'] = e['property']['type']
205 signals['member'] = path['name']
206 signal.append(signals)
207 event['signal'] = signal
208
Matthew Barthd4d0f082017-05-16 13:51:10 -0500209 events.append(event)
210
211 return events
212
213
Matt Spinler78498c92017-04-11 13:59:46 -0500214def getFansInZone(zone_num, profiles, fan_data):
215 """
216 Parses the fan definition YAML files to find the fans
217 that match both the zone passed in and one of the
218 cooling profiles.
219 """
220
221 fans = []
222
223 for f in fan_data['fans']:
224
225 if zone_num != f['cooling_zone']:
226 continue
227
Gunnar Mills67e95512017-06-02 14:35:18 -0500228 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500229 if f.get('cooling_profile') is None:
230 profile = "all"
231 else:
232 profile = f['cooling_profile']
233
234 if profile not in profiles:
235 continue
236
237 fan = {}
238 fan['name'] = f['inventory']
239 fan['sensors'] = f['sensors']
240 fans.append(fan)
241
242 return fans
243
244
Gunnar Millsee8a2812017-06-02 14:26:47 -0500245def getConditionInZoneConditions(zone_condition, zone_conditions_data):
246 """
247 Parses the zone conditions definition YAML files to find the condition
248 that match both the zone condition passed in.
249 """
250
251 condition = {}
252
253 for c in zone_conditions_data['conditions']:
254
255 if zone_condition != c['name']:
256 continue
257 condition['type'] = c['type']
258 properties = []
259 for p in c['properties']:
260 property = {}
261 property['property'] = p['property']
262 property['interface'] = p['interface']
263 property['path'] = p['path']
264 property['type'] = p['type'].lower()
265 property['value'] = str(p['value']).lower()
266 properties.append(property)
267 condition['properties'] = properties
268
269 return condition
270
271
272def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
Matt Spinler78498c92017-04-11 13:59:46 -0500273 """
274 Combines the zone definition YAML and fan
275 definition YAML to create a data structure defining
276 the fan cooling zones.
277 """
278
279 zone_groups = []
280
281 for group in zone_data:
282 conditions = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500283 # zone conditions are optional
284 if 'zone_conditions' in group and group['zone_conditions'] is not None:
285 for c in group['zone_conditions']:
286
287 if not zone_conditions_data:
Gunnar Millsb751f322017-06-06 15:14:11 -0500288 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500289 "zone_conditions used in zone YAML")
290
291 condition = getConditionInZoneConditions(c['name'],
292 zone_conditions_data)
293
294 if not condition:
295 sys.exit("Missing zone condition " + c['name'])
296
297 conditions.append(condition)
Matt Spinler78498c92017-04-11 13:59:46 -0500298
299 zone_group = {}
300 zone_group['conditions'] = conditions
301
302 zones = []
303 for z in group['zones']:
304 zone = {}
305
Gunnar Mills67e95512017-06-02 14:35:18 -0500306 # 'zone' is required
307 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500308 sys.exit("Missing fan zone number in " + zone_yaml)
309
310 zone['num'] = z['zone']
311
312 zone['full_speed'] = z['full_speed']
313
Matthew Barth1de66622017-06-12 13:13:02 -0500314 zone['default_floor'] = z['default_floor']
315
Matthew Bartha9561842017-06-29 11:43:45 -0500316 # 'increase_delay' is optional (use 0 by default)
317 key = 'increase_delay'
318 zone[key] = z.setdefault(key, 0)
319
320 # 'decrease_interval' is optional (use 0 by default)
321 key = 'decrease_interval'
322 zone[key] = z.setdefault(key, 0)
323
Gunnar Mills67e95512017-06-02 14:35:18 -0500324 # 'cooling_profiles' is optional (use 'all' instead)
325 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500326 (z['cooling_profiles'] is None):
327 profiles = ["all"]
328 else:
329 profiles = z['cooling_profiles']
330
331 fans = getFansInZone(z['zone'], profiles, fan_data)
Gunnar Millsb751f322017-06-06 15:14:11 -0500332 events = getEventsInZone(z['zone'], group['zone_conditions'],
333 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500334
335 if len(fans) == 0:
336 sys.exit("Didn't find any fans in zone " + str(zone['num']))
337
338 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500339 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500340 zones.append(zone)
341
342 zone_group['zones'] = zones
343 zone_groups.append(zone_group)
344
345 return zone_groups
346
347
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500348if __name__ == '__main__':
349 parser = ArgumentParser(
350 description="Phosphor fan zone definition parser")
351
352 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
353 default="example/zones.yaml",
354 help='fan zone definitional yaml')
355 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
356 default="example/fans.yaml",
357 help='fan definitional yaml')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500358 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
359 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500360 parser.add_argument('-c', '--zone_conditions_yaml',
361 dest='zone_conditions_yaml',
362 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500363 parser.add_argument('-o', '--output_dir', dest='output_dir',
364 default=".",
365 help='output directory')
366 args = parser.parse_args()
367
368 if not args.zone_yaml or not args.fan_yaml:
369 parser.print_usage()
370 sys.exit(-1)
371
372 with open(args.zone_yaml, 'r') as zone_input:
373 zone_data = yaml.safe_load(zone_input) or {}
374
375 with open(args.fan_yaml, 'r') as fan_input:
376 fan_data = yaml.safe_load(fan_input) or {}
377
Matthew Barthd4d0f082017-05-16 13:51:10 -0500378 events_data = {}
379 if args.events_yaml:
380 with open(args.events_yaml, 'r') as events_input:
381 events_data = yaml.safe_load(events_input) or {}
382
Gunnar Millsee8a2812017-06-02 14:26:47 -0500383 zone_conditions_data = {}
384 if args.zone_conditions_yaml:
385 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
386 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
387
Matt Spinleree7f6422017-05-09 11:03:14 -0500388 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500389 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500390
391 manager_config = zone_data.get('manager_configuration', {})
392
393 if manager_config.get('power_on_delay') is None:
394 manager_config['power_on_delay'] = 0
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500395
396 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
397 with open(output_file, 'w') as output:
Matt Spinleree7f6422017-05-09 11:03:14 -0500398 output.write(Template(tmpl).render(zones=zone_config,
399 mgr_data=manager_config))