blob: adef86ab44d22bd7be1b6a0d908cc64aaf4414ee [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 Barth9c726672017-05-18 13:44:46 -050053 std::vector<FanDefinition>{
54 %for fan in zone['fans']:
55 FanDefinition{
56 "${fan['name']}",
57 std::vector<std::string>{
58 %for sensor in fan['sensors']:
59 "${sensor}",
60 %endfor
61 }
62 },
63 %endfor
64 },
65 std::vector<SetSpeedEvent>{
66 %for event in zone['events']:
67 SetSpeedEvent{
68 Group{
69 %for member in event['group']:
70 {
71 "${member['name']}",
72 {"${member['interface']}",
73 "${member['property']}"}
74 },
75 %endfor
76 },
77 make_action(action::${event['action']['name']}(
78 %for i, p in enumerate(event['action']['parameters']):
79 %if (i+1) != len(event['action']['parameters']):
80 static_cast<${p['type']}>(${p['value']}),
81 %else:
82 static_cast<${p['type']}>(${p['value']})
83 %endif
84 %endfor
85 )),
86 std::vector<PropertyChange>{
87 %for s in event['signal']:
88 PropertyChange{
Matthew Barth34f1bda2017-05-31 13:45:36 -050089 interface("org.freedesktop.DBus.Properties") +
90 member("PropertiesChanged") +
91 type::signal() +
92 path("${s['path']}") +
93 arg0namespace("${s['interface']}"),
Matthew Barth9c726672017-05-18 13:44:46 -050094 make_handler(propertySignal<${s['type']}>(
95 "${s['interface']}",
96 "${s['property']}",
97 handler::setProperty<${s['type']}>(
98 "${s['member']}",
Matthew Barthcec5ab72017-06-02 15:20:56 -050099 "${s['interface']}",
Matthew Barth9c726672017-05-18 13:44:46 -0500100 "${s['property']}"
101 )
102 ))
103 },
104 %endfor
105 }
106 },
107 %endfor
108 }
109 },
110 %endfor
111 }
Matt Spinler78498c92017-04-11 13:59:46 -0500112 },
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500113%endfor
114};
115'''
116
Matt Spinler78498c92017-04-11 13:59:46 -0500117
Matthew Barthbb12c922017-06-13 13:57:40 -0500118def convertToMap(listOfDict):
119 """
120 Converts a list of dictionary entries to a std::map initialization list.
121 """
122 listOfDict = listOfDict.replace('[', '{')
123 listOfDict = listOfDict.replace(']', '}')
124 listOfDict = listOfDict.replace(':', ',')
125 return listOfDict
126
127
Gunnar Millsb751f322017-06-06 15:14:11 -0500128def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500129 """
130 Constructs the event entries defined for each zone using the events yaml
131 provided.
132 """
133 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500134
Matthew Barthd4d0f082017-05-16 13:51:10 -0500135 if 'events' in events_data:
136 for e in events_data['events']:
Gunnar Millsb751f322017-06-06 15:14:11 -0500137
138 # Zone numbers are optional in the events yaml but skip if this
139 # zone's zone number is not in the event's zone numbers
140 if all('zones' in z and z['zones'] is not None and
141 zone_num not in z['zones'] for z in e['zone_conditions']):
142 continue
143
144 # Zone conditions are optional in the events yaml but skip if this
145 # event's condition is not in this zone's conditions
146 if all('name' in z and z['name'] is not None and
147 not any(c['name'] == z['name'] for c in zone_conditions)
148 for z in e['zone_conditions']):
149 continue
Matthew Barthd4d0f082017-05-16 13:51:10 -0500150
151 event = {}
152 # Add set speed event group
153 group = []
154 groups = next(g for g in events_data['groups']
155 if g['name'] == e['group'])
156 for member in groups['members']:
157 members = {}
Matthew Barth7e527c12017-05-17 10:14:15 -0500158 members['type'] = groups['type']
Matthew Barthd4d0f082017-05-16 13:51:10 -0500159 members['name'] = ("/xyz/openbmc_project/" +
160 groups['type'] +
161 member)
162 members['interface'] = e['interface']
163 members['property'] = e['property']['name']
164 group.append(members)
165 event['group'] = group
Matthew Barthba102b32017-05-16 16:13:56 -0500166
167 # Add set speed action and function parameters
168 action = {}
169 actions = next(a for a in events_data['actions']
Matthew Barth9c726672017-05-18 13:44:46 -0500170 if a['name'] == e['action']['name'])
Matthew Barthba102b32017-05-16 16:13:56 -0500171 action['name'] = actions['name']
172 params = []
173 for p in actions['parameters']:
174 param = {}
175 if type(e['action'][p]) is not dict:
176 if p == 'property':
177 param['value'] = str(e['action'][p]).lower()
178 param['type'] = str(e['property']['type']).lower()
179 else:
180 # Default type to 'size_t' when not given
181 param['value'] = str(e['action'][p]).lower()
182 param['type'] = 'size_t'
183 params.append(param)
184 else:
Matthew Barthba102b32017-05-16 16:13:56 -0500185 param['type'] = str(e['action'][p]['type']).lower()
Matthew Barthbb12c922017-06-13 13:57:40 -0500186 if p != 'map':
187 param['value'] = str(e['action'][p]['value']).lower()
188 else:
189 emap = convertToMap(str(e['action'][p]['value']))
190 param['value'] = param['type'] + emap
Matthew Barthba102b32017-05-16 16:13:56 -0500191 params.append(param)
192 action['parameters'] = params
193 event['action'] = action
194
Matthew Barth7e527c12017-05-17 10:14:15 -0500195 # Add property change signal handler
196 signal = []
197 for path in group:
198 signals = {}
199 signals['path'] = path['name']
200 signals['interface'] = e['interface']
201 signals['property'] = e['property']['name']
202 signals['type'] = e['property']['type']
203 signals['member'] = path['name']
204 signal.append(signals)
205 event['signal'] = signal
206
Matthew Barthd4d0f082017-05-16 13:51:10 -0500207 events.append(event)
208
209 return events
210
211
Matt Spinler78498c92017-04-11 13:59:46 -0500212def getFansInZone(zone_num, profiles, fan_data):
213 """
214 Parses the fan definition YAML files to find the fans
215 that match both the zone passed in and one of the
216 cooling profiles.
217 """
218
219 fans = []
220
221 for f in fan_data['fans']:
222
223 if zone_num != f['cooling_zone']:
224 continue
225
Gunnar Mills67e95512017-06-02 14:35:18 -0500226 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500227 if f.get('cooling_profile') is None:
228 profile = "all"
229 else:
230 profile = f['cooling_profile']
231
232 if profile not in profiles:
233 continue
234
235 fan = {}
236 fan['name'] = f['inventory']
237 fan['sensors'] = f['sensors']
238 fans.append(fan)
239
240 return fans
241
242
Gunnar Millsee8a2812017-06-02 14:26:47 -0500243def getConditionInZoneConditions(zone_condition, zone_conditions_data):
244 """
245 Parses the zone conditions definition YAML files to find the condition
246 that match both the zone condition passed in.
247 """
248
249 condition = {}
250
251 for c in zone_conditions_data['conditions']:
252
253 if zone_condition != c['name']:
254 continue
255 condition['type'] = c['type']
256 properties = []
257 for p in c['properties']:
258 property = {}
259 property['property'] = p['property']
260 property['interface'] = p['interface']
261 property['path'] = p['path']
262 property['type'] = p['type'].lower()
263 property['value'] = str(p['value']).lower()
264 properties.append(property)
265 condition['properties'] = properties
266
267 return condition
268
269
270def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
Matt Spinler78498c92017-04-11 13:59:46 -0500271 """
272 Combines the zone definition YAML and fan
273 definition YAML to create a data structure defining
274 the fan cooling zones.
275 """
276
277 zone_groups = []
278
279 for group in zone_data:
280 conditions = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500281 # zone conditions are optional
282 if 'zone_conditions' in group and group['zone_conditions'] is not None:
283 for c in group['zone_conditions']:
284
285 if not zone_conditions_data:
Gunnar Millsb751f322017-06-06 15:14:11 -0500286 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500287 "zone_conditions used in zone YAML")
288
289 condition = getConditionInZoneConditions(c['name'],
290 zone_conditions_data)
291
292 if not condition:
293 sys.exit("Missing zone condition " + c['name'])
294
295 conditions.append(condition)
Matt Spinler78498c92017-04-11 13:59:46 -0500296
297 zone_group = {}
298 zone_group['conditions'] = conditions
299
300 zones = []
301 for z in group['zones']:
302 zone = {}
303
Gunnar Mills67e95512017-06-02 14:35:18 -0500304 # 'zone' is required
305 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500306 sys.exit("Missing fan zone number in " + zone_yaml)
307
308 zone['num'] = z['zone']
309
310 zone['full_speed'] = z['full_speed']
311
Matthew Barth1de66622017-06-12 13:13:02 -0500312 zone['default_floor'] = z['default_floor']
313
Gunnar Mills67e95512017-06-02 14:35:18 -0500314 # 'cooling_profiles' is optional (use 'all' instead)
315 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500316 (z['cooling_profiles'] is None):
317 profiles = ["all"]
318 else:
319 profiles = z['cooling_profiles']
320
321 fans = getFansInZone(z['zone'], profiles, fan_data)
Gunnar Millsb751f322017-06-06 15:14:11 -0500322 events = getEventsInZone(z['zone'], group['zone_conditions'],
323 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500324
325 if len(fans) == 0:
326 sys.exit("Didn't find any fans in zone " + str(zone['num']))
327
328 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500329 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500330 zones.append(zone)
331
332 zone_group['zones'] = zones
333 zone_groups.append(zone_group)
334
335 return zone_groups
336
337
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500338if __name__ == '__main__':
339 parser = ArgumentParser(
340 description="Phosphor fan zone definition parser")
341
342 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
343 default="example/zones.yaml",
344 help='fan zone definitional yaml')
345 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
346 default="example/fans.yaml",
347 help='fan definitional yaml')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500348 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
349 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500350 parser.add_argument('-c', '--zone_conditions_yaml',
351 dest='zone_conditions_yaml',
352 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500353 parser.add_argument('-o', '--output_dir', dest='output_dir',
354 default=".",
355 help='output directory')
356 args = parser.parse_args()
357
358 if not args.zone_yaml or not args.fan_yaml:
359 parser.print_usage()
360 sys.exit(-1)
361
362 with open(args.zone_yaml, 'r') as zone_input:
363 zone_data = yaml.safe_load(zone_input) or {}
364
365 with open(args.fan_yaml, 'r') as fan_input:
366 fan_data = yaml.safe_load(fan_input) or {}
367
Matthew Barthd4d0f082017-05-16 13:51:10 -0500368 events_data = {}
369 if args.events_yaml:
370 with open(args.events_yaml, 'r') as events_input:
371 events_data = yaml.safe_load(events_input) or {}
372
Gunnar Millsee8a2812017-06-02 14:26:47 -0500373 zone_conditions_data = {}
374 if args.zone_conditions_yaml:
375 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
376 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
377
Matt Spinleree7f6422017-05-09 11:03:14 -0500378 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500379 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500380
381 manager_config = zone_data.get('manager_configuration', {})
382
383 if manager_config.get('power_on_delay') is None:
384 manager_config['power_on_delay'] = 0
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500385
386 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
387 with open(output_file, 'w') as output:
Matt Spinleree7f6422017-05-09 11:03:14 -0500388 output.write(Template(tmpl).render(zones=zone_config,
389 mgr_data=manager_config))