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