blob: 3de2188df3d703e15b6ab65a0d77399bfb619671 [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
Matt Spinler848799f2021-07-01 12:43:07 -060023#include <nlohmann/json.hpp>
24
25#include <algorithm>
Patrick Williamsfbf47032023-07-17 12:27:34 -050026#include <format>
Matt Spinler848799f2021-07-01 12:43:07 -060027
28namespace phosphor::fan::control::json
29{
30
31using json = nlohmann::json;
32
Matt Spinlera17d5cc2022-02-02 13:13:30 -060033template <typename T>
34uint64_t addFloorOffset(uint64_t floor, T offset, const std::string& actionName)
35{
36 if constexpr (!std::is_arithmetic_v<T>)
37 {
38 throw std::runtime_error("Invalid variant type in addFloorOffset");
39 }
40
41 auto newFloor = static_cast<T>(floor) + offset;
42 if (newFloor < 0)
43 {
44 log<level::ERR>(
Patrick Williamsfbf47032023-07-17 12:27:34 -050045 std::format("{}: Floor offset of {} resulted in negative floor",
Matt Spinlera17d5cc2022-02-02 13:13:30 -060046 actionName, offset)
47 .c_str());
48 return floor;
49 }
50
51 return static_cast<uint64_t>(newFloor);
52}
53
Matt Spinler848799f2021-07-01 12:43:07 -060054MappedFloor::MappedFloor(const json& jsonObj,
55 const std::vector<Group>& groups) :
56 ActionBase(jsonObj, groups)
57{
58 setKeyGroup(jsonObj);
59 setFloorTable(jsonObj);
Matt Spinler76ef2012022-02-03 16:11:38 -060060 setDefaultFloor(jsonObj);
Matt Spinler1a555602022-12-08 13:19:35 -060061 setCondition(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(),
Patrick Williamsfbf47032023-07-17 12:27:34 -050074 std::format("Group name {} is not a valid group", name)};
Matt Spinler848799f2021-07-01 12:43:07 -060075 }
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 {
Matt Spinler848799f2021-07-01 12:43:07 -0600160 throw ActionParseError{
161 ActionBase::getName(),
162 "Missing value or floor entries in "
163 "actions/fan_floors/floors/floors JSON"};
164 }
165
166 auto value = getJsonValue(floorEntry["value"]);
167 auto floor = floorEntry["floor"].get<uint64_t>();
168
169 fg.floorEntries.emplace_back(std::move(value),
170 std::move(floor));
171 }
172
173 ff.floorGroups.push_back(std::move(fg));
174 }
175
176 _fanFloors.push_back(std::move(ff));
177 }
178}
179
Matt Spinler1a555602022-12-08 13:19:35 -0600180void MappedFloor::setCondition(const json& jsonObj)
181{
182 // condition_group, condition_value, and condition_op
183 // are optional, though they must show up together.
184 // Assume if condition_group is present then they all
185 // must be.
186 if (!jsonObj.contains("condition_group"))
187 {
188 return;
189 }
190
191 _conditionGroup = getGroup(jsonObj["condition_group"].get<std::string>());
192
193 if (_conditionGroup->getMembers().size() != 1)
194 {
195 throw ActionParseError{
196 ActionBase::getName(),
Patrick Williamsfbf47032023-07-17 12:27:34 -0500197 std::format("condition_group {} must only have 1 member",
Matt Spinler1a555602022-12-08 13:19:35 -0600198 _conditionGroup->getName())};
199 }
200
201 if (!jsonObj.contains("condition_value"))
202 {
203 throw ActionParseError{ActionBase::getName(),
204 "Missing required 'condition_value' entry in "
205 "mapped_floor action"};
206 }
207
208 _conditionValue = getJsonValue(jsonObj["condition_value"]);
209
210 if (!jsonObj.contains("condition_op"))
211 {
212 throw ActionParseError{ActionBase::getName(),
213 "Missing required 'condition_op' entry in "
214 "mapped_floor action"};
215 }
216
217 _conditionOp = jsonObj["condition_op"].get<std::string>();
218
219 if ((_conditionOp != "equal") && (_conditionOp != "not_equal"))
220 {
221 throw ActionParseError{ActionBase::getName(),
222 "Invalid 'condition_op' value in "
223 "mapped_floor action"};
224 }
225}
226
Matt Spinler848799f2021-07-01 12:43:07 -0600227/**
228 * @brief Converts the variant to a double if it's a
229 * int32_t or int64_t.
230 */
231void tryConvertToDouble(PropertyVariantType& value)
232{
233 std::visit(
234 [&value](auto&& val) {
Patrick Williams61b73292023-05-10 07:50:12 -0500235 using V = std::decay_t<decltype(val)>;
236 if constexpr (std::is_same_v<int32_t, V> || std::is_same_v<int64_t, V>)
237 {
238 value = static_cast<double>(val);
239 }
Patrick Williams5e15c3b2023-10-20 11:18:11 -0500240 },
Matt Spinler848799f2021-07-01 12:43:07 -0600241 value);
242}
243
244std::optional<PropertyVariantType>
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400245 MappedFloor::getMaxGroupValue(const Group& group)
Matt Spinler848799f2021-07-01 12:43:07 -0600246{
247 std::optional<PropertyVariantType> max;
248 bool checked = false;
249
250 for (const auto& member : group.getMembers())
251 {
252 try
253 {
254 auto value = Manager::getObjValueVariant(
255 member, group.getInterface(), group.getProperty());
256
257 // Only allow a group to have multiple members if it's numeric.
Matt Spinler76ef2012022-02-03 16:11:38 -0600258 // Unlike std::is_arithmetic, bools are not considered numeric
259 // here.
Matt Spinler848799f2021-07-01 12:43:07 -0600260 if (!checked && (group.getMembers().size() > 1))
261 {
262 std::visit(
263 [&group, this](auto&& val) {
Patrick Williams61b73292023-05-10 07:50:12 -0500264 using V = std::decay_t<decltype(val)>;
265 if constexpr (!std::is_same_v<double, V> &&
266 !std::is_same_v<int32_t, V> &&
267 !std::is_same_v<int64_t, V>)
268 {
Patrick Williamsfbf47032023-07-17 12:27:34 -0500269 throw std::runtime_error{std::format(
Patrick Williams61b73292023-05-10 07:50:12 -0500270 "{}: Group {} has more than one member but "
271 "isn't numeric",
272 ActionBase::getName(), group.getName())};
273 }
Patrick Williams5e15c3b2023-10-20 11:18:11 -0500274 },
Matt Spinler848799f2021-07-01 12:43:07 -0600275 value);
276 checked = true;
277 }
278
279 if (max && (value > max))
280 {
281 max = value;
282 }
283 else if (!max)
284 {
285 max = value;
286 }
287 }
288 catch (const std::out_of_range& e)
289 {
290 // Property not there, continue on
291 }
292 }
293
294 if (max)
295 {
296 tryConvertToDouble(*max);
297 }
298
299 return max;
300}
301
Matt Spinler1a555602022-12-08 13:19:35 -0600302bool MappedFloor::meetsCondition()
303{
304 if (!_conditionGroup)
305 {
306 return true;
307 }
308
309 bool meets = false;
310
311 // setCondition() also checks these
312 assert(_conditionGroup->getMembers().size() == 1);
313 assert((_conditionOp == "equal") || (_conditionOp == "not_equal"));
314
315 const auto& member = _conditionGroup->getMembers()[0];
316
317 try
318 {
319 auto value =
320 Manager::getObjValueVariant(member, _conditionGroup->getInterface(),
321 _conditionGroup->getProperty());
322
323 if ((_conditionOp == "equal") && (value == _conditionValue))
324 {
325 meets = true;
326 }
327 else if ((_conditionOp == "not_equal") && (value != _conditionValue))
328 {
329 meets = true;
330 }
331 }
332 catch (const std::out_of_range& e)
333 {
334 // Property not there, so consider it failing the 'equal'
335 // condition and passing the 'not_equal' condition.
336 if (_conditionOp == "equal")
337 {
338 meets = false;
339 }
340 else // not_equal
341 {
342 meets = true;
343 }
344 }
345
346 return meets;
347}
348
Matt Spinler848799f2021-07-01 12:43:07 -0600349void MappedFloor::run(Zone& zone)
350{
Matt Spinler1a555602022-12-08 13:19:35 -0600351 if (!meetsCondition())
352 {
353 // Make sure this no longer has a floor hold
354 if (zone.hasFloorHold(getUniqueName()))
355 {
356 zone.setFloorHold(getUniqueName(), 0, false);
357 }
358 return;
359 }
360
Matt Spinler848799f2021-07-01 12:43:07 -0600361 std::optional<uint64_t> newFloor;
Matt Spinler848799f2021-07-01 12:43:07 -0600362
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400363 auto keyValue = getMaxGroupValue(*_keyGroup);
Matt Spinler848799f2021-07-01 12:43:07 -0600364 if (!keyValue)
365 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600366 auto floor = _defaultFloor ? *_defaultFloor : zone.getDefaultFloor();
367 zone.setFloorHold(getUniqueName(), floor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600368 return;
369 }
370
371 for (const auto& floorTable : _fanFloors)
372 {
373 // First, find the floorTable entry to use based on the key value.
374 auto tableKeyValue = floorTable.keyValue;
375
376 // Convert numeric values from the JSON to doubles so they can
377 // be compared to values coming from D-Bus.
378 tryConvertToDouble(tableKeyValue);
379
380 // The key value from D-Bus must be less than the value
381 // in the table for this entry to be valid.
382 if (*keyValue >= tableKeyValue)
383 {
384 continue;
385 }
386
387 // Now check each group in the tables
Matt Spinlerc981bb52021-09-21 08:31:14 -0500388 for (const auto& [groupOrParameter, floorGroups] :
389 floorTable.floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600390 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500391 std::optional<PropertyVariantType> propertyValue;
392
393 if (std::holds_alternative<std::string>(groupOrParameter))
394 {
395 propertyValue = Manager::getParameter(
396 std::get<std::string>(groupOrParameter));
397 if (propertyValue)
398 {
399 tryConvertToDouble(*propertyValue);
400 }
401 else
402 {
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600403 // If the parameter isn't there, then don't use
404 // this floor table
405 log<level::DEBUG>(
Patrick Williamsfbf47032023-07-17 12:27:34 -0500406 std::format("{}: Parameter {} specified in the JSON "
Matt Spinlerc981bb52021-09-21 08:31:14 -0500407 "could not be found",
408 ActionBase::getName(),
409 std::get<std::string>(groupOrParameter))
410 .c_str());
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600411 continue;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500412 }
413 }
414 else
415 {
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400416 propertyValue =
417 getMaxGroupValue(*std::get<const Group*>(groupOrParameter));
Matt Spinlerc981bb52021-09-21 08:31:14 -0500418 }
419
Matt Spinler848799f2021-07-01 12:43:07 -0600420 std::optional<uint64_t> floor;
Matt Spinler76ef2012022-02-03 16:11:38 -0600421 if (propertyValue)
Matt Spinler848799f2021-07-01 12:43:07 -0600422 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600423 // Do either a <= or an == check depending on the data type
424 // to get the floor value based on this group.
425 for (const auto& [tableValue, tableFloor] : floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600426 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600427 PropertyVariantType value{tableValue};
428 tryConvertToDouble(value);
429
430 if (std::holds_alternative<double>(*propertyValue))
431 {
432 if (*propertyValue <= value)
433 {
434 floor = tableFloor;
435 break;
436 }
437 }
438 else if (*propertyValue == value)
Matt Spinler848799f2021-07-01 12:43:07 -0600439 {
440 floor = tableFloor;
441 break;
442 }
443 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600444 }
445
446 // No floor found in this group, use a default floor for now but
447 // let keep going in case it finds a higher one.
448 if (!floor)
449 {
450 if (floorTable.defaultFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600451 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600452 floor = *floorTable.defaultFloor;
453 }
454 else if (_defaultFloor)
455 {
456 floor = *_defaultFloor;
457 }
458 else
459 {
460 floor = zone.getDefaultFloor();
Matt Spinler848799f2021-07-01 12:43:07 -0600461 }
462 }
463
464 // Keep track of the highest floor value found across all
465 // entries/groups
Matt Spinler76ef2012022-02-03 16:11:38 -0600466 if ((newFloor && (floor > *newFloor)) || !newFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600467 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600468 newFloor = floor;
Matt Spinler848799f2021-07-01 12:43:07 -0600469 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600470 }
471
472 // if still no floor, use the default one from the floor table if
473 // there
474 if (!newFloor && floorTable.defaultFloor)
475 {
476 newFloor = floorTable.defaultFloor.value();
Matt Spinler848799f2021-07-01 12:43:07 -0600477 }
478
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600479 if (newFloor)
480 {
481 *newFloor = applyFloorOffset(*newFloor, floorTable.offsetParameter);
482 }
483
Matt Spinler848799f2021-07-01 12:43:07 -0600484 // Valid key value for this entry, so done
485 break;
486 }
487
Matt Spinler76ef2012022-02-03 16:11:38 -0600488 if (!newFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600489 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600490 newFloor = _defaultFloor ? *_defaultFloor : zone.getDefaultFloor();
Matt Spinler848799f2021-07-01 12:43:07 -0600491 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600492
493 zone.setFloorHold(getUniqueName(), *newFloor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600494}
495
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600496uint64_t MappedFloor::applyFloorOffset(uint64_t floor,
497 const std::string& offsetParameter) const
498{
499 if (!offsetParameter.empty())
500 {
501 auto offset = Manager::getParameter(offsetParameter);
502 if (offset)
503 {
504 if (std::holds_alternative<int32_t>(*offset))
505 {
506 return addFloorOffset(floor, std::get<int32_t>(*offset),
507 getUniqueName());
508 }
509 else if (std::holds_alternative<int64_t>(*offset))
510 {
511 return addFloorOffset(floor, std::get<int64_t>(*offset),
512 getUniqueName());
513 }
514 else if (std::holds_alternative<double>(*offset))
515 {
516 return addFloorOffset(floor, std::get<double>(*offset),
517 getUniqueName());
518 }
519 else
520 {
521 throw std::runtime_error(
522 "Invalid data type in floor offset parameter ");
523 }
524 }
525 }
526
527 return floor;
528}
529
Matt Spinler848799f2021-07-01 12:43:07 -0600530} // namespace phosphor::fan::control::json