1#!/usr/bin/env python3
2
3import os
4import sys
5import yaml
6from argparse import ArgumentParser
7from mako.template import Template
8
9"""
10This script generates the data structures for the
11phosphor-fan-monitor application.
12
13A future improvement is to get the fan inventory names
14from a separate file, so just that file could be generated
15from the MRW.
16"""
17
18
19tmpl = '''\
20<%!
21def indent(str, depth):
22    return ''.join(4*' '*depth+line for line in str.splitlines(True))
23%>\
24<%def name="getCondParams(cond)" buffered="True">
25%if (cond['name'] == 'propertiesMatch'):
26std::vector<PropertyState>{
27    %for i in cond['properties']:
28    PropertyState{
29        {
30            "${i['object']}",
31            "${i['interface']}",
32            "${i['property']['name']}"
33        },
34        static_cast<${i['property']['type']}>(${str(i['property']['value']).lower()})
35    },
36    %endfor
37}
38%endif
39</%def>\
40/* This is a generated file. */
41#include "fan_defs.hpp"
42#include "types.hpp"
43#include "groups.hpp"
44#include "conditions.hpp"
45
46using namespace phosphor::fan::monitor;
47using namespace phosphor::fan::trust;
48
49const std::vector<FanDefinition> fanDefinitions
50{
51%for fan_data in data.get('fans', {}):
52    FanDefinition{"${fan_data['inventory']}",
53                  ${fan_data.get('method', {})},
54                  ${fan_data.get('functional_delay', 0)},
55                  ${fan_data.get('allowed_out_of_range_time', {})},
56                  ${fan_data['deviation']},
57                  ${fan_data['num_sensors_nonfunc_for_fan_nonfunc']},
58                  0, // Monitor start delay - not used in YAML configs
59                  0, // Count interval - not used in YAML configs
60                  std::nullopt, // nonfuncRotorErrorDelay - also not used here
61                  std::nullopt, // fanMissingErrorDelay - also not used here
62                  std::vector<SensorDefinition>{
63                  %for sensor in fan_data['sensors']:
64                  <%
65                      #has_target is a bool, and we need a true instead of True
66                      has_target = str(sensor['has_target']).lower()
67                      target_interface = sensor.get(
68                          'target_interface',
69                          'xyz.openbmc_project.Control.FanSpeed')
70                      target_path = sensor.get(
71                          'target_path',
72                          '')
73                      factor = sensor.get('factor', 1)
74                      offset = sensor.get('offset', 0)
75                      threshold = sensor.get('threshold', 1)
76                      ignore_above_max = str(sensor.get(
77                          'ignore_above_max', False)).lower()
78                  %> \
79                      SensorDefinition{"${sensor['name']}",
80                                       ${has_target},
81                                       "${target_interface}",
82                                       "${target_path}",
83                                       ${factor},
84                                       ${offset},
85                                       ${threshold},
86                                       ${ignore_above_max}},
87                  %endfor
88                  },
89                  %if ('condition' in fan_data) and \
90                  (fan_data['condition'] is not None):
91                  make_condition(condition::${fan_data['condition']['name']}(\
92                      ${indent(getCondParams(cond=fan_data['condition']), 5)}\
93                  ))
94                  %else:
95                  {},
96                  false // set_func_on_present. Hardcoded to false.
97                  %endif
98    },
99%endfor
100};
101
102##Function to generate the group creation lambda.
103##If a group were to ever need a different constructor,
104##it could be handled here.
105<%def name="get_lambda_contents(group)">
106            std::vector<GroupDefinition> group{
107            %for member in group['group']:
108            <%
109                in_trust = str(member.get('in_trust', "true")).lower()
110            %>
111                GroupDefinition{"${member['name']}", ${in_trust}},
112            %endfor
113            };
114            return std::make_unique<${group['class']}>(group);
115</%def>
116const std::vector<CreateGroupFunction> trustGroups
117{
118%for group in data.get('sensor_trust_groups', {}):
119    {
120        []()
121        {\
122${get_lambda_contents(group)}\
123        }
124    },
125%endfor
126};
127'''
128
129
130if __name__ == '__main__':
131    parser = ArgumentParser(
132        description="Phosphor fan monitor definition parser")
133
134    parser.add_argument('-m', '--monitor_yaml', dest='monitor_yaml',
135                        default="example/monitor.yaml",
136                        help='fan monitor definitional yaml')
137    parser.add_argument('-o', '--output_dir', dest='output_dir',
138                        default=".",
139                        help='output directory')
140    args = parser.parse_args()
141
142    if not args.monitor_yaml:
143        parser.print_usage()
144        sys.exit(1)
145
146    with open(args.monitor_yaml, 'r') as monitor_input:
147        monitor_data = yaml.safe_load(monitor_input) or {}
148
149    #Do some minor input validation
150    for fan in monitor_data.get('fans', {}):
151        if ((fan['deviation'] < 0) or (fan['deviation'] > 100)):
152            sys.exit("Invalid deviation value " + str(fan['deviation']))
153
154    output_file = os.path.join(args.output_dir, "fan_monitor_defs.cpp")
155    with open(output_file, 'w') as output:
156        output.write(Template(tmpl).render(data=monitor_data))
157