blob: 55414ac6cdac04d9481a97860bb6f114160e8423 [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
Matt Spinlera17d5cc2022-02-02 13:13:30 -060034template <typename T>
35uint64_t addFloorOffset(uint64_t floor, T offset, const std::string& actionName)
36{
37 if constexpr (!std::is_arithmetic_v<T>)
38 {
39 throw std::runtime_error("Invalid variant type in addFloorOffset");
40 }
41
42 auto newFloor = static_cast<T>(floor) + offset;
43 if (newFloor < 0)
44 {
45 log<level::ERR>(
46 fmt::format("{}: Floor offset of {} resulted in negative floor",
47 actionName, offset)
48 .c_str());
49 return floor;
50 }
51
52 return static_cast<uint64_t>(newFloor);
53}
54
Matt Spinler848799f2021-07-01 12:43:07 -060055MappedFloor::MappedFloor(const json& jsonObj,
56 const std::vector<Group>& groups) :
57 ActionBase(jsonObj, groups)
58{
59 setKeyGroup(jsonObj);
60 setFloorTable(jsonObj);
Matt Spinler76ef2012022-02-03 16:11:38 -060061 setDefaultFloor(jsonObj);
Matt Spinler848799f2021-07-01 12:43:07 -060062}
63
64const Group* MappedFloor::getGroup(const std::string& name)
65{
66 auto groupIt =
67 find_if(_groups.begin(), _groups.end(),
68 [name](const auto& group) { return name == group.getName(); });
69
70 if (groupIt == _groups.end())
71 {
72 throw ActionParseError{
73 ActionBase::getName(),
74 fmt::format("Group name {} is not a valid group", name)};
75 }
76
77 return &(*groupIt);
78}
79
80void MappedFloor::setKeyGroup(const json& jsonObj)
81{
82 if (!jsonObj.contains("key_group"))
83 {
84 throw ActionParseError{ActionBase::getName(),
85 "Missing required 'key_group' entry"};
86 }
87 _keyGroup = getGroup(jsonObj["key_group"].get<std::string>());
88}
89
Matt Spinler76ef2012022-02-03 16:11:38 -060090void MappedFloor::setDefaultFloor(const json& jsonObj)
91{
92 if (jsonObj.contains("default_floor"))
93 {
94 _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
95 }
96}
97
Matt Spinler848799f2021-07-01 12:43:07 -060098void MappedFloor::setFloorTable(const json& jsonObj)
99{
100 if (!jsonObj.contains("fan_floors"))
101 {
102 throw ActionParseError{ActionBase::getName(),
103 "Missing fan_floors JSON entry"};
104 }
105
106 const auto& fanFloors = jsonObj.at("fan_floors");
107
108 for (const auto& floors : fanFloors)
109 {
110 if (!floors.contains("key") || !floors.contains("floors"))
111 {
112 throw ActionParseError{
113 ActionBase::getName(),
114 "Missing key or floors entries in actions/fan_floors JSON"};
115 }
116
117 FanFloors ff;
118 ff.keyValue = getJsonValue(floors["key"]);
119
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600120 if (floors.contains("floor_offset_parameter"))
121 {
122 ff.offsetParameter =
123 floors["floor_offset_parameter"].get<std::string>();
124 }
125
Matt Spinler76ef2012022-02-03 16:11:38 -0600126 if (floors.contains("default_floor"))
127 {
128 ff.defaultFloor = floors["default_floor"].get<uint64_t>();
129 }
130
Matt Spinler848799f2021-07-01 12:43:07 -0600131 for (const auto& groupEntry : floors["floors"])
132 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500133 if ((!groupEntry.contains("group") &&
134 !groupEntry.contains("parameter")) ||
135 !groupEntry.contains("floors"))
Matt Spinler848799f2021-07-01 12:43:07 -0600136 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500137 throw ActionParseError{
138 ActionBase::getName(),
139 "Missing group, parameter, or floors entries in "
140 "actions/fan_floors/floors JSON"};
Matt Spinler848799f2021-07-01 12:43:07 -0600141 }
142
143 FloorGroup fg;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500144 if (groupEntry.contains("group"))
145 {
146 fg.groupOrParameter =
147 getGroup(groupEntry["group"].get<std::string>());
148 }
149 else
150 {
151 fg.groupOrParameter =
152 groupEntry["parameter"].get<std::string>();
153 }
Matt Spinler848799f2021-07-01 12:43:07 -0600154
155 for (const auto& floorEntry : groupEntry["floors"])
156 {
157 if (!floorEntry.contains("value") ||
158 !floorEntry.contains("floor"))
159 {
160
161 throw ActionParseError{
162 ActionBase::getName(),
163 "Missing value or floor entries in "
164 "actions/fan_floors/floors/floors JSON"};
165 }
166
167 auto value = getJsonValue(floorEntry["value"]);
168 auto floor = floorEntry["floor"].get<uint64_t>();
169
170 fg.floorEntries.emplace_back(std::move(value),
171 std::move(floor));
172 }
173
174 ff.floorGroups.push_back(std::move(fg));
175 }
176
177 _fanFloors.push_back(std::move(ff));
178 }
179}
180
181/**
182 * @brief Converts the variant to a double if it's a
183 * int32_t or int64_t.
184 */
185void tryConvertToDouble(PropertyVariantType& value)
186{
187 std::visit(
188 [&value](auto&& val) {
189 using V = std::decay_t<decltype(val)>;
190 if constexpr (std::is_same_v<int32_t, V> ||
191 std::is_same_v<int64_t, V>)
192 {
193 value = static_cast<double>(val);
194 }
195 },
196 value);
197}
198
199std::optional<PropertyVariantType>
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400200 MappedFloor::getMaxGroupValue(const Group& group)
Matt Spinler848799f2021-07-01 12:43:07 -0600201{
202 std::optional<PropertyVariantType> max;
203 bool checked = false;
204
205 for (const auto& member : group.getMembers())
206 {
207 try
208 {
209 auto value = Manager::getObjValueVariant(
210 member, group.getInterface(), group.getProperty());
211
212 // Only allow a group to have multiple members if it's numeric.
Matt Spinler76ef2012022-02-03 16:11:38 -0600213 // Unlike std::is_arithmetic, bools are not considered numeric
214 // here.
Matt Spinler848799f2021-07-01 12:43:07 -0600215 if (!checked && (group.getMembers().size() > 1))
216 {
217 std::visit(
218 [&group, this](auto&& val) {
219 using V = std::decay_t<decltype(val)>;
220 if constexpr (!std::is_same_v<double, V> &&
221 !std::is_same_v<int32_t, V> &&
222 !std::is_same_v<int64_t, V>)
223 {
224 throw std::runtime_error{fmt::format(
225 "{}: Group {} has more than one member but "
226 "isn't numeric",
227 ActionBase::getName(), group.getName())};
228 }
229 },
230 value);
231 checked = true;
232 }
233
234 if (max && (value > max))
235 {
236 max = value;
237 }
238 else if (!max)
239 {
240 max = value;
241 }
242 }
243 catch (const std::out_of_range& e)
244 {
245 // Property not there, continue on
246 }
247 }
248
249 if (max)
250 {
251 tryConvertToDouble(*max);
252 }
253
254 return max;
255}
256
257void MappedFloor::run(Zone& zone)
258{
259 std::optional<uint64_t> newFloor;
Matt Spinler848799f2021-07-01 12:43:07 -0600260
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400261 auto keyValue = getMaxGroupValue(*_keyGroup);
Matt Spinler848799f2021-07-01 12:43:07 -0600262 if (!keyValue)
263 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600264 auto floor = _defaultFloor ? *_defaultFloor : zone.getDefaultFloor();
265 zone.setFloorHold(getUniqueName(), floor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600266 return;
267 }
268
269 for (const auto& floorTable : _fanFloors)
270 {
271 // First, find the floorTable entry to use based on the key value.
272 auto tableKeyValue = floorTable.keyValue;
273
274 // Convert numeric values from the JSON to doubles so they can
275 // be compared to values coming from D-Bus.
276 tryConvertToDouble(tableKeyValue);
277
278 // The key value from D-Bus must be less than the value
279 // in the table for this entry to be valid.
280 if (*keyValue >= tableKeyValue)
281 {
282 continue;
283 }
284
285 // Now check each group in the tables
Matt Spinlerc981bb52021-09-21 08:31:14 -0500286 for (const auto& [groupOrParameter, floorGroups] :
287 floorTable.floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600288 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500289 std::optional<PropertyVariantType> propertyValue;
290
291 if (std::holds_alternative<std::string>(groupOrParameter))
292 {
293 propertyValue = Manager::getParameter(
294 std::get<std::string>(groupOrParameter));
295 if (propertyValue)
296 {
297 tryConvertToDouble(*propertyValue);
298 }
299 else
300 {
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600301 // If the parameter isn't there, then don't use
302 // this floor table
303 log<level::DEBUG>(
Matt Spinlerc981bb52021-09-21 08:31:14 -0500304 fmt::format("{}: Parameter {} specified in the JSON "
305 "could not be found",
306 ActionBase::getName(),
307 std::get<std::string>(groupOrParameter))
308 .c_str());
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600309 continue;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500310 }
311 }
312 else
313 {
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400314 propertyValue =
315 getMaxGroupValue(*std::get<const Group*>(groupOrParameter));
Matt Spinlerc981bb52021-09-21 08:31:14 -0500316 }
317
Matt Spinler848799f2021-07-01 12:43:07 -0600318 std::optional<uint64_t> floor;
Matt Spinler76ef2012022-02-03 16:11:38 -0600319 if (propertyValue)
Matt Spinler848799f2021-07-01 12:43:07 -0600320 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600321 // Do either a <= or an == check depending on the data type
322 // to get the floor value based on this group.
323 for (const auto& [tableValue, tableFloor] : floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600324 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600325 PropertyVariantType value{tableValue};
326 tryConvertToDouble(value);
327
328 if (std::holds_alternative<double>(*propertyValue))
329 {
330 if (*propertyValue <= value)
331 {
332 floor = tableFloor;
333 break;
334 }
335 }
336 else if (*propertyValue == value)
Matt Spinler848799f2021-07-01 12:43:07 -0600337 {
338 floor = tableFloor;
339 break;
340 }
341 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600342 }
343
344 // No floor found in this group, use a default floor for now but
345 // let keep going in case it finds a higher one.
346 if (!floor)
347 {
348 if (floorTable.defaultFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600349 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600350 floor = *floorTable.defaultFloor;
351 }
352 else if (_defaultFloor)
353 {
354 floor = *_defaultFloor;
355 }
356 else
357 {
358 floor = zone.getDefaultFloor();
Matt Spinler848799f2021-07-01 12:43:07 -0600359 }
360 }
361
362 // Keep track of the highest floor value found across all
363 // entries/groups
Matt Spinler76ef2012022-02-03 16:11:38 -0600364 if ((newFloor && (floor > *newFloor)) || !newFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600365 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600366 newFloor = floor;
Matt Spinler848799f2021-07-01 12:43:07 -0600367 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600368 }
369
370 // if still no floor, use the default one from the floor table if
371 // there
372 if (!newFloor && floorTable.defaultFloor)
373 {
374 newFloor = floorTable.defaultFloor.value();
Matt Spinler848799f2021-07-01 12:43:07 -0600375 }
376
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600377 if (newFloor)
378 {
379 *newFloor = applyFloorOffset(*newFloor, floorTable.offsetParameter);
380 }
381
Matt Spinler848799f2021-07-01 12:43:07 -0600382 // Valid key value for this entry, so done
383 break;
384 }
385
Matt Spinler76ef2012022-02-03 16:11:38 -0600386 if (!newFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600387 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600388 newFloor = _defaultFloor ? *_defaultFloor : zone.getDefaultFloor();
Matt Spinler848799f2021-07-01 12:43:07 -0600389 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600390
391 zone.setFloorHold(getUniqueName(), *newFloor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600392}
393
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600394uint64_t MappedFloor::applyFloorOffset(uint64_t floor,
395 const std::string& offsetParameter) const
396{
397 if (!offsetParameter.empty())
398 {
399 auto offset = Manager::getParameter(offsetParameter);
400 if (offset)
401 {
402 if (std::holds_alternative<int32_t>(*offset))
403 {
404 return addFloorOffset(floor, std::get<int32_t>(*offset),
405 getUniqueName());
406 }
407 else if (std::holds_alternative<int64_t>(*offset))
408 {
409 return addFloorOffset(floor, std::get<int64_t>(*offset),
410 getUniqueName());
411 }
412 else if (std::holds_alternative<double>(*offset))
413 {
414 return addFloorOffset(floor, std::get<double>(*offset),
415 getUniqueName());
416 }
417 else
418 {
419 throw std::runtime_error(
420 "Invalid data type in floor offset parameter ");
421 }
422 }
423 }
424
425 return floor;
426}
427
Matt Spinler848799f2021-07-01 12:43:07 -0600428} // namespace phosphor::fan::control::json