blob: 694477b91b35d4a29f20d592dda14f84278fba47 [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']}",
98 "${s['property']}"
99 )
100 ))
101 },
102 %endfor
103 }
104 },
105 %endfor
106 }
107 },
108 %endfor
109 }
Matt Spinler78498c92017-04-11 13:59:46 -0500110 },
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500111%endfor
112};
113'''
114
Matt Spinler78498c92017-04-11 13:59:46 -0500115
Gunnar Millsb751f322017-06-06 15:14:11 -0500116def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500117 """
118 Constructs the event entries defined for each zone using the events yaml
119 provided.
120 """
121 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500122
Matthew Barthd4d0f082017-05-16 13:51:10 -0500123 if 'events' in events_data:
124 for e in events_data['events']:
Gunnar Millsb751f322017-06-06 15:14:11 -0500125
126 # Zone numbers are optional in the events yaml but skip if this
127 # zone's zone number is not in the event's zone numbers
128 if all('zones' in z and z['zones'] is not None and
129 zone_num not in z['zones'] for z in e['zone_conditions']):
130 continue
131
132 # Zone conditions are optional in the events yaml but skip if this
133 # event's condition is not in this zone's conditions
134 if all('name' in z and z['name'] is not None and
135 not any(c['name'] == z['name'] for c in zone_conditions)
136 for z in e['zone_conditions']):
137 continue
Matthew Barthd4d0f082017-05-16 13:51:10 -0500138
139 event = {}
140 # Add set speed event group
141 group = []
142 groups = next(g for g in events_data['groups']
143 if g['name'] == e['group'])
144 for member in groups['members']:
145 members = {}
Matthew Barth7e527c12017-05-17 10:14:15 -0500146 members['type'] = groups['type']
Matthew Barthd4d0f082017-05-16 13:51:10 -0500147 members['name'] = ("/xyz/openbmc_project/" +
148 groups['type'] +
149 member)
150 members['interface'] = e['interface']
151 members['property'] = e['property']['name']
152 group.append(members)
153 event['group'] = group
Matthew Barthba102b32017-05-16 16:13:56 -0500154
155 # Add set speed action and function parameters
156 action = {}
157 actions = next(a for a in events_data['actions']
Matthew Barth9c726672017-05-18 13:44:46 -0500158 if a['name'] == e['action']['name'])
Matthew Barthba102b32017-05-16 16:13:56 -0500159 action['name'] = actions['name']
160 params = []
161 for p in actions['parameters']:
162 param = {}
163 if type(e['action'][p]) is not dict:
164 if p == 'property':
165 param['value'] = str(e['action'][p]).lower()
166 param['type'] = str(e['property']['type']).lower()
167 else:
168 # Default type to 'size_t' when not given
169 param['value'] = str(e['action'][p]).lower()
170 param['type'] = 'size_t'
171 params.append(param)
172 else:
173 param['value'] = str(e['action'][p]['value']).lower()
174 param['type'] = str(e['action'][p]['type']).lower()
175 params.append(param)
176 action['parameters'] = params
177 event['action'] = action
178
Matthew Barth7e527c12017-05-17 10:14:15 -0500179 # Add property change signal handler
180 signal = []
181 for path in group:
182 signals = {}
183 signals['path'] = path['name']
184 signals['interface'] = e['interface']
185 signals['property'] = e['property']['name']
186 signals['type'] = e['property']['type']
187 signals['member'] = path['name']
188 signal.append(signals)
189 event['signal'] = signal
190
Matthew Barthd4d0f082017-05-16 13:51:10 -0500191 events.append(event)
192
193 return events
194
195
Matt Spinler78498c92017-04-11 13:59:46 -0500196def getFansInZone(zone_num, profiles, fan_data):
197 """
198 Parses the fan definition YAML files to find the fans
199 that match both the zone passed in and one of the
200 cooling profiles.
201 """
202
203 fans = []
204
205 for f in fan_data['fans']:
206
207 if zone_num != f['cooling_zone']:
208 continue
209
Gunnar Mills67e95512017-06-02 14:35:18 -0500210 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500211 if f.get('cooling_profile') is None:
212 profile = "all"
213 else:
214 profile = f['cooling_profile']
215
216 if profile not in profiles:
217 continue
218
219 fan = {}
220 fan['name'] = f['inventory']
221 fan['sensors'] = f['sensors']
222 fans.append(fan)
223
224 return fans
225
226
Gunnar Millsee8a2812017-06-02 14:26:47 -0500227def getConditionInZoneConditions(zone_condition, zone_conditions_data):
228 """
229 Parses the zone conditions definition YAML files to find the condition
230 that match both the zone condition passed in.
231 """
232
233 condition = {}
234
235 for c in zone_conditions_data['conditions']:
236
237 if zone_condition != c['name']:
238 continue
239 condition['type'] = c['type']
240 properties = []
241 for p in c['properties']:
242 property = {}
243 property['property'] = p['property']
244 property['interface'] = p['interface']
245 property['path'] = p['path']
246 property['type'] = p['type'].lower()
247 property['value'] = str(p['value']).lower()
248 properties.append(property)
249 condition['properties'] = properties
250
251 return condition
252
253
254def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
Matt Spinler78498c92017-04-11 13:59:46 -0500255 """
256 Combines the zone definition YAML and fan
257 definition YAML to create a data structure defining
258 the fan cooling zones.
259 """
260
261 zone_groups = []
262
263 for group in zone_data:
264 conditions = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500265 # zone conditions are optional
266 if 'zone_conditions' in group and group['zone_conditions'] is not None:
267 for c in group['zone_conditions']:
268
269 if not zone_conditions_data:
Gunnar Millsb751f322017-06-06 15:14:11 -0500270 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500271 "zone_conditions used in zone YAML")
272
273 condition = getConditionInZoneConditions(c['name'],
274 zone_conditions_data)
275
276 if not condition:
277 sys.exit("Missing zone condition " + c['name'])
278
279 conditions.append(condition)
Matt Spinler78498c92017-04-11 13:59:46 -0500280
281 zone_group = {}
282 zone_group['conditions'] = conditions
283
284 zones = []
285 for z in group['zones']:
286 zone = {}
287
Gunnar Mills67e95512017-06-02 14:35:18 -0500288 # 'zone' is required
289 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500290 sys.exit("Missing fan zone number in " + zone_yaml)
291
292 zone['num'] = z['zone']
293
294 zone['full_speed'] = z['full_speed']
295
Gunnar Mills67e95512017-06-02 14:35:18 -0500296 # 'cooling_profiles' is optional (use 'all' instead)
297 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500298 (z['cooling_profiles'] is None):
299 profiles = ["all"]
300 else:
301 profiles = z['cooling_profiles']
302
303 fans = getFansInZone(z['zone'], profiles, fan_data)
Gunnar Millsb751f322017-06-06 15:14:11 -0500304 events = getEventsInZone(z['zone'], group['zone_conditions'],
305 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500306
307 if len(fans) == 0:
308 sys.exit("Didn't find any fans in zone " + str(zone['num']))
309
310 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500311 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500312 zones.append(zone)
313
314 zone_group['zones'] = zones
315 zone_groups.append(zone_group)
316
317 return zone_groups
318
319
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500320if __name__ == '__main__':
321 parser = ArgumentParser(
322 description="Phosphor fan zone definition parser")
323
324 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
325 default="example/zones.yaml",
326 help='fan zone definitional yaml')
327 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
328 default="example/fans.yaml",
329 help='fan definitional yaml')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500330 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
331 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500332 parser.add_argument('-c', '--zone_conditions_yaml',
333 dest='zone_conditions_yaml',
334 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500335 parser.add_argument('-o', '--output_dir', dest='output_dir',
336 default=".",
337 help='output directory')
338 args = parser.parse_args()
339
340 if not args.zone_yaml or not args.fan_yaml:
341 parser.print_usage()
342 sys.exit(-1)
343
344 with open(args.zone_yaml, 'r') as zone_input:
345 zone_data = yaml.safe_load(zone_input) or {}
346
347 with open(args.fan_yaml, 'r') as fan_input:
348 fan_data = yaml.safe_load(fan_input) or {}
349
Matthew Barthd4d0f082017-05-16 13:51:10 -0500350 events_data = {}
351 if args.events_yaml:
352 with open(args.events_yaml, 'r') as events_input:
353 events_data = yaml.safe_load(events_input) or {}
354
Gunnar Millsee8a2812017-06-02 14:26:47 -0500355 zone_conditions_data = {}
356 if args.zone_conditions_yaml:
357 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
358 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
359
Matt Spinleree7f6422017-05-09 11:03:14 -0500360 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500361 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500362
363 manager_config = zone_data.get('manager_configuration', {})
364
365 if manager_config.get('power_on_delay') is None:
366 manager_config['power_on_delay'] = 0
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500367
368 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
369 with open(output_file, 'w') as output:
Matt Spinleree7f6422017-05-09 11:03:14 -0500370 output.write(Template(tmpl).render(zones=zone_config,
371 mgr_data=manager_config))