blob: 20c15342fef2d1c8daaed80efb20d52614da8f76 [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']},
52 std::vector<FanDefinition>{
53 %for fan in zone['fans']:
54 FanDefinition{
55 "${fan['name']}",
56 std::vector<std::string>{
57 %for sensor in fan['sensors']:
58 "${sensor}",
59 %endfor
60 }
61 },
62 %endfor
63 },
64 std::vector<SetSpeedEvent>{
65 %for event in zone['events']:
66 SetSpeedEvent{
67 Group{
68 %for member in event['group']:
69 {
70 "${member['name']}",
71 {"${member['interface']}",
72 "${member['property']}"}
73 },
74 %endfor
75 },
76 make_action(action::${event['action']['name']}(
77 %for i, p in enumerate(event['action']['parameters']):
78 %if (i+1) != len(event['action']['parameters']):
79 static_cast<${p['type']}>(${p['value']}),
80 %else:
81 static_cast<${p['type']}>(${p['value']})
82 %endif
83 %endfor
84 )),
85 std::vector<PropertyChange>{
86 %for s in event['signal']:
87 PropertyChange{
Matthew Barth34f1bda2017-05-31 13:45:36 -050088 interface("org.freedesktop.DBus.Properties") +
89 member("PropertiesChanged") +
90 type::signal() +
91 path("${s['path']}") +
92 arg0namespace("${s['interface']}"),
Matthew Barth9c726672017-05-18 13:44:46 -050093 make_handler(propertySignal<${s['type']}>(
94 "${s['interface']}",
95 "${s['property']}",
96 handler::setProperty<${s['type']}>(
97 "${s['member']}",
Matthew Barthcec5ab72017-06-02 15:20:56 -050098 "${s['interface']}",
Matthew Barth9c726672017-05-18 13:44:46 -050099 "${s['property']}"
100 )
101 ))
102 },
103 %endfor
104 }
105 },
106 %endfor
107 }
108 },
109 %endfor
110 }
Matt Spinler78498c92017-04-11 13:59:46 -0500111 },
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500112%endfor
113};
114'''
115
Matt Spinler78498c92017-04-11 13:59:46 -0500116
Gunnar Millsb751f322017-06-06 15:14:11 -0500117def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500118 """
119 Constructs the event entries defined for each zone using the events yaml
120 provided.
121 """
122 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500123
Matthew Barthd4d0f082017-05-16 13:51:10 -0500124 if 'events' in events_data:
125 for e in events_data['events']:
Gunnar Millsb751f322017-06-06 15:14:11 -0500126
127 # Zone numbers are optional in the events yaml but skip if this
128 # zone's zone number is not in the event's zone numbers
129 if all('zones' in z and z['zones'] is not None and
130 zone_num not in z['zones'] for z in e['zone_conditions']):
131 continue
132
133 # Zone conditions are optional in the events yaml but skip if this
134 # event's condition is not in this zone's conditions
135 if all('name' in z and z['name'] is not None and
136 not any(c['name'] == z['name'] for c in zone_conditions)
137 for z in e['zone_conditions']):
138 continue
Matthew Barthd4d0f082017-05-16 13:51:10 -0500139
140 event = {}
141 # Add set speed event group
142 group = []
143 groups = next(g for g in events_data['groups']
144 if g['name'] == e['group'])
145 for member in groups['members']:
146 members = {}
Matthew Barth7e527c12017-05-17 10:14:15 -0500147 members['type'] = groups['type']
Matthew Barthd4d0f082017-05-16 13:51:10 -0500148 members['name'] = ("/xyz/openbmc_project/" +
149 groups['type'] +
150 member)
151 members['interface'] = e['interface']
152 members['property'] = e['property']['name']
153 group.append(members)
154 event['group'] = group
Matthew Barthba102b32017-05-16 16:13:56 -0500155
156 # Add set speed action and function parameters
157 action = {}
158 actions = next(a for a in events_data['actions']
Matthew Barth9c726672017-05-18 13:44:46 -0500159 if a['name'] == e['action']['name'])
Matthew Barthba102b32017-05-16 16:13:56 -0500160 action['name'] = actions['name']
161 params = []
162 for p in actions['parameters']:
163 param = {}
164 if type(e['action'][p]) is not dict:
165 if p == 'property':
166 param['value'] = str(e['action'][p]).lower()
167 param['type'] = str(e['property']['type']).lower()
168 else:
169 # Default type to 'size_t' when not given
170 param['value'] = str(e['action'][p]).lower()
171 param['type'] = 'size_t'
172 params.append(param)
173 else:
174 param['value'] = str(e['action'][p]['value']).lower()
175 param['type'] = str(e['action'][p]['type']).lower()
176 params.append(param)
177 action['parameters'] = params
178 event['action'] = action
179
Matthew Barth7e527c12017-05-17 10:14:15 -0500180 # Add property change signal handler
181 signal = []
182 for path in group:
183 signals = {}
184 signals['path'] = path['name']
185 signals['interface'] = e['interface']
186 signals['property'] = e['property']['name']
187 signals['type'] = e['property']['type']
188 signals['member'] = path['name']
189 signal.append(signals)
190 event['signal'] = signal
191
Matthew Barthd4d0f082017-05-16 13:51:10 -0500192 events.append(event)
193
194 return events
195
196
Matt Spinler78498c92017-04-11 13:59:46 -0500197def getFansInZone(zone_num, profiles, fan_data):
198 """
199 Parses the fan definition YAML files to find the fans
200 that match both the zone passed in and one of the
201 cooling profiles.
202 """
203
204 fans = []
205
206 for f in fan_data['fans']:
207
208 if zone_num != f['cooling_zone']:
209 continue
210
Gunnar Mills67e95512017-06-02 14:35:18 -0500211 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500212 if f.get('cooling_profile') is None:
213 profile = "all"
214 else:
215 profile = f['cooling_profile']
216
217 if profile not in profiles:
218 continue
219
220 fan = {}
221 fan['name'] = f['inventory']
222 fan['sensors'] = f['sensors']
223 fans.append(fan)
224
225 return fans
226
227
Gunnar Millsee8a2812017-06-02 14:26:47 -0500228def getConditionInZoneConditions(zone_condition, zone_conditions_data):
229 """
230 Parses the zone conditions definition YAML files to find the condition
231 that match both the zone condition passed in.
232 """
233
234 condition = {}
235
236 for c in zone_conditions_data['conditions']:
237
238 if zone_condition != c['name']:
239 continue
240 condition['type'] = c['type']
241 properties = []
242 for p in c['properties']:
243 property = {}
244 property['property'] = p['property']
245 property['interface'] = p['interface']
246 property['path'] = p['path']
247 property['type'] = p['type'].lower()
248 property['value'] = str(p['value']).lower()
249 properties.append(property)
250 condition['properties'] = properties
251
252 return condition
253
254
255def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
Matt Spinler78498c92017-04-11 13:59:46 -0500256 """
257 Combines the zone definition YAML and fan
258 definition YAML to create a data structure defining
259 the fan cooling zones.
260 """
261
262 zone_groups = []
263
264 for group in zone_data:
265 conditions = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500266 # zone conditions are optional
267 if 'zone_conditions' in group and group['zone_conditions'] is not None:
268 for c in group['zone_conditions']:
269
270 if not zone_conditions_data:
Gunnar Millsb751f322017-06-06 15:14:11 -0500271 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500272 "zone_conditions used in zone YAML")
273
274 condition = getConditionInZoneConditions(c['name'],
275 zone_conditions_data)
276
277 if not condition:
278 sys.exit("Missing zone condition " + c['name'])
279
280 conditions.append(condition)
Matt Spinler78498c92017-04-11 13:59:46 -0500281
282 zone_group = {}
283 zone_group['conditions'] = conditions
284
285 zones = []
286 for z in group['zones']:
287 zone = {}
288
Gunnar Mills67e95512017-06-02 14:35:18 -0500289 # 'zone' is required
290 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500291 sys.exit("Missing fan zone number in " + zone_yaml)
292
293 zone['num'] = z['zone']
294
295 zone['full_speed'] = z['full_speed']
296
Gunnar Mills67e95512017-06-02 14:35:18 -0500297 # 'cooling_profiles' is optional (use 'all' instead)
298 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500299 (z['cooling_profiles'] is None):
300 profiles = ["all"]
301 else:
302 profiles = z['cooling_profiles']
303
304 fans = getFansInZone(z['zone'], profiles, fan_data)
Gunnar Millsb751f322017-06-06 15:14:11 -0500305 events = getEventsInZone(z['zone'], group['zone_conditions'],
306 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500307
308 if len(fans) == 0:
309 sys.exit("Didn't find any fans in zone " + str(zone['num']))
310
311 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500312 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500313 zones.append(zone)
314
315 zone_group['zones'] = zones
316 zone_groups.append(zone_group)
317
318 return zone_groups
319
320
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500321if __name__ == '__main__':
322 parser = ArgumentParser(
323 description="Phosphor fan zone definition parser")
324
325 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
326 default="example/zones.yaml",
327 help='fan zone definitional yaml')
328 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
329 default="example/fans.yaml",
330 help='fan definitional yaml')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500331 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
332 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500333 parser.add_argument('-c', '--zone_conditions_yaml',
334 dest='zone_conditions_yaml',
335 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500336 parser.add_argument('-o', '--output_dir', dest='output_dir',
337 default=".",
338 help='output directory')
339 args = parser.parse_args()
340
341 if not args.zone_yaml or not args.fan_yaml:
342 parser.print_usage()
343 sys.exit(-1)
344
345 with open(args.zone_yaml, 'r') as zone_input:
346 zone_data = yaml.safe_load(zone_input) or {}
347
348 with open(args.fan_yaml, 'r') as fan_input:
349 fan_data = yaml.safe_load(fan_input) or {}
350
Matthew Barthd4d0f082017-05-16 13:51:10 -0500351 events_data = {}
352 if args.events_yaml:
353 with open(args.events_yaml, 'r') as events_input:
354 events_data = yaml.safe_load(events_input) or {}
355
Gunnar Millsee8a2812017-06-02 14:26:47 -0500356 zone_conditions_data = {}
357 if args.zone_conditions_yaml:
358 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
359 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
360
Matt Spinleree7f6422017-05-09 11:03:14 -0500361 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500362 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500363
364 manager_config = zone_data.get('manager_configuration', {})
365
366 if manager_config.get('power_on_delay') is None:
367 manager_config['power_on_delay'] = 0
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500368
369 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
370 with open(output_file, 'w') as output:
Matt Spinleree7f6422017-05-09 11:03:14 -0500371 output.write(Template(tmpl).render(zones=zone_config,
372 mgr_data=manager_config))