blob: 2f077f1bdef65ddf40cbbc7044303d137985b9b3 [file] [log] [blame]
Matt Spinler848799f2021-07-01 12:43:07 -06001/**
2 * Copyright © 2021 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16#include "mapped_floor.hpp"
17
18#include "../manager.hpp"
19#include "../zone.hpp"
20#include "group.hpp"
21#include "sdeventplus.hpp"
22
23#include <fmt/format.h>
24
25#include <nlohmann/json.hpp>
26
27#include <algorithm>
28
29namespace phosphor::fan::control::json
30{
31
32using json = nlohmann::json;
33
34MappedFloor::MappedFloor(const json& jsonObj,
35 const std::vector<Group>& groups) :
36 ActionBase(jsonObj, groups)
37{
38 setKeyGroup(jsonObj);
39 setFloorTable(jsonObj);
40}
41
42const Group* MappedFloor::getGroup(const std::string& name)
43{
44 auto groupIt =
45 find_if(_groups.begin(), _groups.end(),
46 [name](const auto& group) { return name == group.getName(); });
47
48 if (groupIt == _groups.end())
49 {
50 throw ActionParseError{
51 ActionBase::getName(),
52 fmt::format("Group name {} is not a valid group", name)};
53 }
54
55 return &(*groupIt);
56}
57
58void MappedFloor::setKeyGroup(const json& jsonObj)
59{
60 if (!jsonObj.contains("key_group"))
61 {
62 throw ActionParseError{ActionBase::getName(),
63 "Missing required 'key_group' entry"};
64 }
65 _keyGroup = getGroup(jsonObj["key_group"].get<std::string>());
66}
67
68void MappedFloor::setFloorTable(const json& jsonObj)
69{
70 if (!jsonObj.contains("fan_floors"))
71 {
72 throw ActionParseError{ActionBase::getName(),
73 "Missing fan_floors JSON entry"};
74 }
75
76 const auto& fanFloors = jsonObj.at("fan_floors");
77
78 for (const auto& floors : fanFloors)
79 {
80 if (!floors.contains("key") || !floors.contains("floors"))
81 {
82 throw ActionParseError{
83 ActionBase::getName(),
84 "Missing key or floors entries in actions/fan_floors JSON"};
85 }
86
87 FanFloors ff;
88 ff.keyValue = getJsonValue(floors["key"]);
89
90 for (const auto& groupEntry : floors["floors"])
91 {
Matt Spinlerc981bb52021-09-21 08:31:14 -050092 if ((!groupEntry.contains("group") &&
93 !groupEntry.contains("parameter")) ||
94 !groupEntry.contains("floors"))
Matt Spinler848799f2021-07-01 12:43:07 -060095 {
Matt Spinlerc981bb52021-09-21 08:31:14 -050096 throw ActionParseError{
97 ActionBase::getName(),
98 "Missing group, parameter, or floors entries in "
99 "actions/fan_floors/floors JSON"};
Matt Spinler848799f2021-07-01 12:43:07 -0600100 }
101
102 FloorGroup fg;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500103 if (groupEntry.contains("group"))
104 {
105 fg.groupOrParameter =
106 getGroup(groupEntry["group"].get<std::string>());
107 }
108 else
109 {
110 fg.groupOrParameter =
111 groupEntry["parameter"].get<std::string>();
112 }
Matt Spinler848799f2021-07-01 12:43:07 -0600113
114 for (const auto& floorEntry : groupEntry["floors"])
115 {
116 if (!floorEntry.contains("value") ||
117 !floorEntry.contains("floor"))
118 {
119
120 throw ActionParseError{
121 ActionBase::getName(),
122 "Missing value or floor entries in "
123 "actions/fan_floors/floors/floors JSON"};
124 }
125
126 auto value = getJsonValue(floorEntry["value"]);
127 auto floor = floorEntry["floor"].get<uint64_t>();
128
129 fg.floorEntries.emplace_back(std::move(value),
130 std::move(floor));
131 }
132
133 ff.floorGroups.push_back(std::move(fg));
134 }
135
136 _fanFloors.push_back(std::move(ff));
137 }
138}
139
140/**
141 * @brief Converts the variant to a double if it's a
142 * int32_t or int64_t.
143 */
144void tryConvertToDouble(PropertyVariantType& value)
145{
146 std::visit(
147 [&value](auto&& val) {
148 using V = std::decay_t<decltype(val)>;
149 if constexpr (std::is_same_v<int32_t, V> ||
150 std::is_same_v<int64_t, V>)
151 {
152 value = static_cast<double>(val);
153 }
154 },
155 value);
156}
157
158std::optional<PropertyVariantType>
159 MappedFloor::getMaxGroupValue(const Group& group, const Manager& manager)
160{
161 std::optional<PropertyVariantType> max;
162 bool checked = false;
163
164 for (const auto& member : group.getMembers())
165 {
166 try
167 {
168 auto value = Manager::getObjValueVariant(
169 member, group.getInterface(), group.getProperty());
170
171 // Only allow a group to have multiple members if it's numeric.
172 // Unlike std::is_arithmetic, bools are not considered numeric here.
173 if (!checked && (group.getMembers().size() > 1))
174 {
175 std::visit(
176 [&group, this](auto&& val) {
177 using V = std::decay_t<decltype(val)>;
178 if constexpr (!std::is_same_v<double, V> &&
179 !std::is_same_v<int32_t, V> &&
180 !std::is_same_v<int64_t, V>)
181 {
182 throw std::runtime_error{fmt::format(
183 "{}: Group {} has more than one member but "
184 "isn't numeric",
185 ActionBase::getName(), group.getName())};
186 }
187 },
188 value);
189 checked = true;
190 }
191
192 if (max && (value > max))
193 {
194 max = value;
195 }
196 else if (!max)
197 {
198 max = value;
199 }
200 }
201 catch (const std::out_of_range& e)
202 {
203 // Property not there, continue on
204 }
205 }
206
207 if (max)
208 {
209 tryConvertToDouble(*max);
210 }
211
212 return max;
213}
214
215void MappedFloor::run(Zone& zone)
216{
217 std::optional<uint64_t> newFloor;
218 bool missingGroupProperty = false;
219 auto& manager = *zone.getManager();
220
221 auto keyValue = getMaxGroupValue(*_keyGroup, manager);
222 if (!keyValue)
223 {
224 zone.setFloor(zone.getDefaultFloor());
225 return;
226 }
227
228 for (const auto& floorTable : _fanFloors)
229 {
230 // First, find the floorTable entry to use based on the key value.
231 auto tableKeyValue = floorTable.keyValue;
232
233 // Convert numeric values from the JSON to doubles so they can
234 // be compared to values coming from D-Bus.
235 tryConvertToDouble(tableKeyValue);
236
237 // The key value from D-Bus must be less than the value
238 // in the table for this entry to be valid.
239 if (*keyValue >= tableKeyValue)
240 {
241 continue;
242 }
243
244 // Now check each group in the tables
Matt Spinlerc981bb52021-09-21 08:31:14 -0500245 for (const auto& [groupOrParameter, floorGroups] :
246 floorTable.floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600247 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500248 std::optional<PropertyVariantType> propertyValue;
249
250 if (std::holds_alternative<std::string>(groupOrParameter))
251 {
252 propertyValue = Manager::getParameter(
253 std::get<std::string>(groupOrParameter));
254 if (propertyValue)
255 {
256 tryConvertToDouble(*propertyValue);
257 }
258 else
259 {
260 log<level::ERR>(
261 fmt::format("{}: Parameter {} specified in the JSON "
262 "could not be found",
263 ActionBase::getName(),
264 std::get<std::string>(groupOrParameter))
265 .c_str());
266 }
267 }
268 else
269 {
270 propertyValue = getMaxGroupValue(
271 *std::get<const Group*>(groupOrParameter), manager);
272 }
273
Matt Spinler848799f2021-07-01 12:43:07 -0600274 if (!propertyValue)
275 {
276 // Couldn't successfully get a value. Results in default floor.
277 missingGroupProperty = true;
278 break;
279 }
280
281 // Do either a <= or an == check depending on the data type to get
282 // the floor value based on this group.
283 std::optional<uint64_t> floor;
284 for (const auto& [tableValue, tableFloor] : floorGroups)
285 {
286 PropertyVariantType value{tableValue};
287 tryConvertToDouble(value);
288
289 if (std::holds_alternative<double>(*propertyValue))
290 {
291 if (*propertyValue <= value)
292 {
293 floor = tableFloor;
294 break;
295 }
296 }
297 else if (*propertyValue == value)
298 {
299 floor = tableFloor;
300 break;
301 }
302 }
303
304 // Keep track of the highest floor value found across all
305 // entries/groups
306 if (floor)
307 {
308 if ((newFloor && (floor > *newFloor)) || !newFloor)
309 {
310 newFloor = floor;
311 }
312 }
313 else
314 {
315 // No match found in this group's table.
316 // Results in default floor.
317 missingGroupProperty = true;
318 }
319 }
320
321 // Valid key value for this entry, so done
322 break;
323 }
324
325 if (newFloor && !missingGroupProperty)
326 {
327 zone.setFloor(*newFloor);
328 }
329 else
330 {
331 zone.setFloor(zone.getDefaultFloor());
332 }
333}
334
335} // namespace phosphor::fan::control::json