1#!/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
14#Note: Condition is a TODO (openbmc/openbmc#1500)
15tmpl = '''/* This is a generated file. */
16#include "manager.hpp"
17
18using namespace phosphor::fan::control;
19
20const unsigned int Manager::_powerOnDelay{${mgr_data['power_on_delay']}};
21
22const std::vector<ZoneGroup> Manager::_zoneLayouts
23{
24%for zone_group in zones:
25    ZoneGroup{std::vector<Condition>{},
26              std::vector<ZoneDefinition>{
27              %for zone in zone_group['zones']:
28                  ZoneDefinition{${zone['num']},
29                                 ${zone['full_speed']},
30                                 std::vector<FanDefinition>{
31                                     %for fan in zone['fans']:
32                                     FanDefinition{"${fan['name']}",
33                                         std::vector<std::string>{
34                                         %for sensor in fan['sensors']:
35                                            "${sensor}",
36                                         %endfor
37                                         }
38                                     },
39                                     %endfor
40                                 }
41                  },
42              %endfor
43              }
44    },
45%endfor
46};
47'''
48
49
50def getFansInZone(zone_num, profiles, fan_data):
51    """
52    Parses the fan definition YAML files to find the fans
53    that match both the zone passed in and one of the
54    cooling profiles.
55    """
56
57    fans = []
58
59    for f in fan_data['fans']:
60
61        if zone_num != f['cooling_zone']:
62            continue
63
64        #'cooling_profile' is optional (use 'all' instead)
65        if f.get('cooling_profile') is None:
66            profile = "all"
67        else:
68            profile = f['cooling_profile']
69
70        if profile not in profiles:
71            continue
72
73        fan = {}
74        fan['name'] = f['inventory']
75        fan['sensors'] = f['sensors']
76        fans.append(fan)
77
78    return fans
79
80
81def buildZoneData(zone_data, fan_data):
82    """
83    Combines the zone definition YAML and fan
84    definition YAML to create a data structure defining
85    the fan cooling zones.
86    """
87
88    zone_groups = []
89
90    for group in zone_data:
91        conditions = []
92        for c in group['zone_conditions']:
93            conditions.append(c['name'])
94
95        zone_group = {}
96        zone_group['conditions'] = conditions
97
98        zones = []
99        for z in group['zones']:
100            zone = {}
101
102            #'zone' is required
103            if (not 'zone' in z) or (z['zone'] is None):
104                sys.exit("Missing fan zone number in " + zone_yaml)
105
106            zone['num'] = z['zone']
107
108            zone['full_speed'] = z['full_speed']
109
110            #'cooling_profiles' is optional (use 'all' instead)
111            if (not 'cooling_profiles' in z) or \
112                    (z['cooling_profiles'] is None):
113                profiles = ["all"]
114            else:
115                profiles = z['cooling_profiles']
116
117            fans = getFansInZone(z['zone'], profiles, fan_data)
118
119            if len(fans) == 0:
120                sys.exit("Didn't find any fans in zone " + str(zone['num']))
121
122            zone['fans'] = fans
123            zones.append(zone)
124
125        zone_group['zones'] = zones
126        zone_groups.append(zone_group)
127
128    return zone_groups
129
130
131if __name__ == '__main__':
132    parser = ArgumentParser(
133        description="Phosphor fan zone definition parser")
134
135    parser.add_argument('-z', '--zone_yaml', dest='zone_yaml',
136                        default="example/zones.yaml",
137                        help='fan zone definitional yaml')
138    parser.add_argument('-f', '--fan_yaml', dest='fan_yaml',
139                        default="example/fans.yaml",
140                        help='fan definitional yaml')
141    parser.add_argument('-o', '--output_dir', dest='output_dir',
142                        default=".",
143                        help='output directory')
144    args = parser.parse_args()
145
146    if not args.zone_yaml or not args.fan_yaml:
147        parser.print_usage()
148        sys.exit(-1)
149
150    with open(args.zone_yaml, 'r') as zone_input:
151        zone_data = yaml.safe_load(zone_input) or {}
152
153    with open(args.fan_yaml, 'r') as fan_input:
154        fan_data = yaml.safe_load(fan_input) or {}
155
156    zone_config = buildZoneData(zone_data.get('zone_configuration', {}),
157                                fan_data)
158
159    manager_config = zone_data.get('manager_configuration', {})
160
161    if manager_config.get('power_on_delay') is None:
162        manager_config['power_on_delay'] = 0
163
164    output_file = os.path.join(args.output_dir, "fan_zone_defs.cpp")
165    with open(output_file, 'w') as output:
166        output.write(Template(tmpl).render(zones=zone_config,
167                     mgr_data=manager_config))
168