blob: eb33956af149392147bb4439e7eb9bfdf91e3fe6 [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
Gunnar Millsb751f322017-06-06 15:14:11 -0500118def getEventsInZone(zone_num, zone_conditions, events_data):
Matthew Barthd4d0f082017-05-16 13:51:10 -0500119 """
120 Constructs the event entries defined for each zone using the events yaml
121 provided.
122 """
123 events = []
Matthew Barthba102b32017-05-16 16:13:56 -0500124
Matthew Barthd4d0f082017-05-16 13:51:10 -0500125 if 'events' in events_data:
126 for e in events_data['events']:
Gunnar Millsb751f322017-06-06 15:14:11 -0500127
128 # Zone numbers are optional in the events yaml but skip if this
129 # zone's zone number is not in the event's zone numbers
130 if all('zones' in z and z['zones'] is not None and
131 zone_num not in z['zones'] for z in e['zone_conditions']):
132 continue
133
134 # Zone conditions are optional in the events yaml but skip if this
135 # event's condition is not in this zone's conditions
136 if all('name' in z and z['name'] is not None and
137 not any(c['name'] == z['name'] for c in zone_conditions)
138 for z in e['zone_conditions']):
139 continue
Matthew Barthd4d0f082017-05-16 13:51:10 -0500140
141 event = {}
142 # Add set speed event group
143 group = []
144 groups = next(g for g in events_data['groups']
145 if g['name'] == e['group'])
146 for member in groups['members']:
147 members = {}
Matthew Barth7e527c12017-05-17 10:14:15 -0500148 members['type'] = groups['type']
Matthew Barthd4d0f082017-05-16 13:51:10 -0500149 members['name'] = ("/xyz/openbmc_project/" +
150 groups['type'] +
151 member)
152 members['interface'] = e['interface']
153 members['property'] = e['property']['name']
154 group.append(members)
155 event['group'] = group
Matthew Barthba102b32017-05-16 16:13:56 -0500156
157 # Add set speed action and function parameters
158 action = {}
159 actions = next(a for a in events_data['actions']
Matthew Barth9c726672017-05-18 13:44:46 -0500160 if a['name'] == e['action']['name'])
Matthew Barthba102b32017-05-16 16:13:56 -0500161 action['name'] = actions['name']
162 params = []
163 for p in actions['parameters']:
164 param = {}
165 if type(e['action'][p]) is not dict:
166 if p == 'property':
167 param['value'] = str(e['action'][p]).lower()
168 param['type'] = str(e['property']['type']).lower()
169 else:
170 # Default type to 'size_t' when not given
171 param['value'] = str(e['action'][p]).lower()
172 param['type'] = 'size_t'
173 params.append(param)
174 else:
175 param['value'] = str(e['action'][p]['value']).lower()
176 param['type'] = str(e['action'][p]['type']).lower()
177 params.append(param)
178 action['parameters'] = params
179 event['action'] = action
180
Matthew Barth7e527c12017-05-17 10:14:15 -0500181 # Add property change signal handler
182 signal = []
183 for path in group:
184 signals = {}
185 signals['path'] = path['name']
186 signals['interface'] = e['interface']
187 signals['property'] = e['property']['name']
188 signals['type'] = e['property']['type']
189 signals['member'] = path['name']
190 signal.append(signals)
191 event['signal'] = signal
192
Matthew Barthd4d0f082017-05-16 13:51:10 -0500193 events.append(event)
194
195 return events
196
197
Matt Spinler78498c92017-04-11 13:59:46 -0500198def getFansInZone(zone_num, profiles, fan_data):
199 """
200 Parses the fan definition YAML files to find the fans
201 that match both the zone passed in and one of the
202 cooling profiles.
203 """
204
205 fans = []
206
207 for f in fan_data['fans']:
208
209 if zone_num != f['cooling_zone']:
210 continue
211
Gunnar Mills67e95512017-06-02 14:35:18 -0500212 # 'cooling_profile' is optional (use 'all' instead)
Matt Spinler78498c92017-04-11 13:59:46 -0500213 if f.get('cooling_profile') is None:
214 profile = "all"
215 else:
216 profile = f['cooling_profile']
217
218 if profile not in profiles:
219 continue
220
221 fan = {}
222 fan['name'] = f['inventory']
223 fan['sensors'] = f['sensors']
224 fans.append(fan)
225
226 return fans
227
228
Gunnar Millsee8a2812017-06-02 14:26:47 -0500229def getConditionInZoneConditions(zone_condition, zone_conditions_data):
230 """
231 Parses the zone conditions definition YAML files to find the condition
232 that match both the zone condition passed in.
233 """
234
235 condition = {}
236
237 for c in zone_conditions_data['conditions']:
238
239 if zone_condition != c['name']:
240 continue
241 condition['type'] = c['type']
242 properties = []
243 for p in c['properties']:
244 property = {}
245 property['property'] = p['property']
246 property['interface'] = p['interface']
247 property['path'] = p['path']
248 property['type'] = p['type'].lower()
249 property['value'] = str(p['value']).lower()
250 properties.append(property)
251 condition['properties'] = properties
252
253 return condition
254
255
256def buildZoneData(zone_data, fan_data, events_data, zone_conditions_data):
Matt Spinler78498c92017-04-11 13:59:46 -0500257 """
258 Combines the zone definition YAML and fan
259 definition YAML to create a data structure defining
260 the fan cooling zones.
261 """
262
263 zone_groups = []
264
265 for group in zone_data:
266 conditions = []
Gunnar Millsee8a2812017-06-02 14:26:47 -0500267 # zone conditions are optional
268 if 'zone_conditions' in group and group['zone_conditions'] is not None:
269 for c in group['zone_conditions']:
270
271 if not zone_conditions_data:
Gunnar Millsb751f322017-06-06 15:14:11 -0500272 sys.exit("No zone_conditions YAML file but " +
Gunnar Millsee8a2812017-06-02 14:26:47 -0500273 "zone_conditions used in zone YAML")
274
275 condition = getConditionInZoneConditions(c['name'],
276 zone_conditions_data)
277
278 if not condition:
279 sys.exit("Missing zone condition " + c['name'])
280
281 conditions.append(condition)
Matt Spinler78498c92017-04-11 13:59:46 -0500282
283 zone_group = {}
284 zone_group['conditions'] = conditions
285
286 zones = []
287 for z in group['zones']:
288 zone = {}
289
Gunnar Mills67e95512017-06-02 14:35:18 -0500290 # 'zone' is required
291 if ('zone' not in z) or (z['zone'] is None):
Matt Spinler78498c92017-04-11 13:59:46 -0500292 sys.exit("Missing fan zone number in " + zone_yaml)
293
294 zone['num'] = z['zone']
295
296 zone['full_speed'] = z['full_speed']
297
Matthew Barth1de66622017-06-12 13:13:02 -0500298 zone['default_floor'] = z['default_floor']
299
Gunnar Mills67e95512017-06-02 14:35:18 -0500300 # 'cooling_profiles' is optional (use 'all' instead)
301 if ('cooling_profiles' not in z) or \
Matt Spinler78498c92017-04-11 13:59:46 -0500302 (z['cooling_profiles'] is None):
303 profiles = ["all"]
304 else:
305 profiles = z['cooling_profiles']
306
307 fans = getFansInZone(z['zone'], profiles, fan_data)
Gunnar Millsb751f322017-06-06 15:14:11 -0500308 events = getEventsInZone(z['zone'], group['zone_conditions'],
309 events_data)
Matt Spinler78498c92017-04-11 13:59:46 -0500310
311 if len(fans) == 0:
312 sys.exit("Didn't find any fans in zone " + str(zone['num']))
313
314 zone['fans'] = fans
Matthew Barthd4d0f082017-05-16 13:51:10 -0500315 zone['events'] = events
Matt Spinler78498c92017-04-11 13:59:46 -0500316 zones.append(zone)
317
318 zone_group['zones'] = zones
319 zone_groups.append(zone_group)
320
321 return zone_groups
322
323
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500324if __name__ == '__main__':
325 parser = ArgumentParser(
326 description="Phosphor fan zone definition parser")
327
328 parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
329 default="example/zones.yaml",
330 help='fan zone definitional yaml')
331 parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
332 default="example/fans.yaml",
333 help='fan definitional yaml')
Matthew Barthd4d0f082017-05-16 13:51:10 -0500334 parser.add_argument('-e', '--events_yaml', dest='events_yaml',
335 help='events to set speeds yaml')
Gunnar Millsee8a2812017-06-02 14:26:47 -0500336 parser.add_argument('-c', '--zone_conditions_yaml',
337 dest='zone_conditions_yaml',
338 help='conditions to determine zone yaml')
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500339 parser.add_argument('-o', '--output_dir', dest='output_dir',
340 default=".",
341 help='output directory')
342 args = parser.parse_args()
343
344 if not args.zone_yaml or not args.fan_yaml:
345 parser.print_usage()
346 sys.exit(-1)
347
348 with open(args.zone_yaml, 'r') as zone_input:
349 zone_data = yaml.safe_load(zone_input) or {}
350
351 with open(args.fan_yaml, 'r') as fan_input:
352 fan_data = yaml.safe_load(fan_input) or {}
353
Matthew Barthd4d0f082017-05-16 13:51:10 -0500354 events_data = {}
355 if args.events_yaml:
356 with open(args.events_yaml, 'r') as events_input:
357 events_data = yaml.safe_load(events_input) or {}
358
Gunnar Millsee8a2812017-06-02 14:26:47 -0500359 zone_conditions_data = {}
360 if args.zone_conditions_yaml:
361 with open(args.zone_conditions_yaml, 'r') as zone_conditions_input:
362 zone_conditions_data = yaml.safe_load(zone_conditions_input) or {}
363
Matt Spinleree7f6422017-05-09 11:03:14 -0500364 zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
Gunnar Millsee8a2812017-06-02 14:26:47 -0500365 fan_data, events_data, zone_conditions_data)
Matt Spinleree7f6422017-05-09 11:03:14 -0500366
367 manager_config = zone_data.get('manager_configuration', {})
368
369 if manager_config.get('power_on_delay') is None:
370 manager_config['power_on_delay'] = 0
Matt Spinlerd08dbe22017-04-11 13:52:54 -0500371
372 output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
373 with open(output_file, 'w') as output:
Matt Spinleree7f6422017-05-09 11:03:14 -0500374 output.write(Template(tmpl).render(zones=zone_config,
375 mgr_data=manager_config))