blob: c947098daf160adf636329b02093e926096f5acc [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 Spinler1a555602022-12-08 13:19:35 -060062 setCondition(jsonObj);
Matt Spinler848799f2021-07-01 12:43:07 -060063}
64
65const Group* MappedFloor::getGroup(const std::string& name)
66{
67 auto groupIt =
68 find_if(_groups.begin(), _groups.end(),
69 [name](const auto& group) { return name == group.getName(); });
70
71 if (groupIt == _groups.end())
72 {
73 throw ActionParseError{
74 ActionBase::getName(),
75 fmt::format("Group name {} is not a valid group", name)};
76 }
77
78 return &(*groupIt);
79}
80
81void MappedFloor::setKeyGroup(const json& jsonObj)
82{
83 if (!jsonObj.contains("key_group"))
84 {
85 throw ActionParseError{ActionBase::getName(),
86 "Missing required 'key_group' entry"};
87 }
88 _keyGroup = getGroup(jsonObj["key_group"].get<std::string>());
89}
90
Matt Spinler76ef2012022-02-03 16:11:38 -060091void MappedFloor::setDefaultFloor(const json& jsonObj)
92{
93 if (jsonObj.contains("default_floor"))
94 {
95 _defaultFloor = jsonObj["default_floor"].get<uint64_t>();
96 }
97}
98
Matt Spinler848799f2021-07-01 12:43:07 -060099void MappedFloor::setFloorTable(const json& jsonObj)
100{
101 if (!jsonObj.contains("fan_floors"))
102 {
103 throw ActionParseError{ActionBase::getName(),
104 "Missing fan_floors JSON entry"};
105 }
106
107 const auto& fanFloors = jsonObj.at("fan_floors");
108
109 for (const auto& floors : fanFloors)
110 {
111 if (!floors.contains("key") || !floors.contains("floors"))
112 {
113 throw ActionParseError{
114 ActionBase::getName(),
115 "Missing key or floors entries in actions/fan_floors JSON"};
116 }
117
118 FanFloors ff;
119 ff.keyValue = getJsonValue(floors["key"]);
120
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600121 if (floors.contains("floor_offset_parameter"))
122 {
123 ff.offsetParameter =
124 floors["floor_offset_parameter"].get<std::string>();
125 }
126
Matt Spinler76ef2012022-02-03 16:11:38 -0600127 if (floors.contains("default_floor"))
128 {
129 ff.defaultFloor = floors["default_floor"].get<uint64_t>();
130 }
131
Matt Spinler848799f2021-07-01 12:43:07 -0600132 for (const auto& groupEntry : floors["floors"])
133 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500134 if ((!groupEntry.contains("group") &&
135 !groupEntry.contains("parameter")) ||
136 !groupEntry.contains("floors"))
Matt Spinler848799f2021-07-01 12:43:07 -0600137 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500138 throw ActionParseError{
139 ActionBase::getName(),
140 "Missing group, parameter, or floors entries in "
141 "actions/fan_floors/floors JSON"};
Matt Spinler848799f2021-07-01 12:43:07 -0600142 }
143
144 FloorGroup fg;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500145 if (groupEntry.contains("group"))
146 {
147 fg.groupOrParameter =
148 getGroup(groupEntry["group"].get<std::string>());
149 }
150 else
151 {
152 fg.groupOrParameter =
153 groupEntry["parameter"].get<std::string>();
154 }
Matt Spinler848799f2021-07-01 12:43:07 -0600155
156 for (const auto& floorEntry : groupEntry["floors"])
157 {
158 if (!floorEntry.contains("value") ||
159 !floorEntry.contains("floor"))
160 {
Matt Spinler848799f2021-07-01 12:43:07 -0600161 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
Matt Spinler1a555602022-12-08 13:19:35 -0600181void MappedFloor::setCondition(const json& jsonObj)
182{
183 // condition_group, condition_value, and condition_op
184 // are optional, though they must show up together.
185 // Assume if condition_group is present then they all
186 // must be.
187 if (!jsonObj.contains("condition_group"))
188 {
189 return;
190 }
191
192 _conditionGroup = getGroup(jsonObj["condition_group"].get<std::string>());
193
194 if (_conditionGroup->getMembers().size() != 1)
195 {
196 throw ActionParseError{
197 ActionBase::getName(),
198 fmt::format("condition_group {} must only have 1 member",
199 _conditionGroup->getName())};
200 }
201
202 if (!jsonObj.contains("condition_value"))
203 {
204 throw ActionParseError{ActionBase::getName(),
205 "Missing required 'condition_value' entry in "
206 "mapped_floor action"};
207 }
208
209 _conditionValue = getJsonValue(jsonObj["condition_value"]);
210
211 if (!jsonObj.contains("condition_op"))
212 {
213 throw ActionParseError{ActionBase::getName(),
214 "Missing required 'condition_op' entry in "
215 "mapped_floor action"};
216 }
217
218 _conditionOp = jsonObj["condition_op"].get<std::string>();
219
220 if ((_conditionOp != "equal") && (_conditionOp != "not_equal"))
221 {
222 throw ActionParseError{ActionBase::getName(),
223 "Invalid 'condition_op' value in "
224 "mapped_floor action"};
225 }
226}
227
Matt Spinler848799f2021-07-01 12:43:07 -0600228/**
229 * @brief Converts the variant to a double if it's a
230 * int32_t or int64_t.
231 */
232void tryConvertToDouble(PropertyVariantType& value)
233{
234 std::visit(
235 [&value](auto&& val) {
Patrick Williams61b73292023-05-10 07:50:12 -0500236 using V = std::decay_t<decltype(val)>;
237 if constexpr (std::is_same_v<int32_t, V> || std::is_same_v<int64_t, V>)
238 {
239 value = static_cast<double>(val);
240 }
Matt Spinler848799f2021-07-01 12:43:07 -0600241 },
242 value);
243}
244
245std::optional<PropertyVariantType>
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400246 MappedFloor::getMaxGroupValue(const Group& group)
Matt Spinler848799f2021-07-01 12:43:07 -0600247{
248 std::optional<PropertyVariantType> max;
249 bool checked = false;
250
251 for (const auto& member : group.getMembers())
252 {
253 try
254 {
255 auto value = Manager::getObjValueVariant(
256 member, group.getInterface(), group.getProperty());
257
258 // Only allow a group to have multiple members if it's numeric.
Matt Spinler76ef2012022-02-03 16:11:38 -0600259 // Unlike std::is_arithmetic, bools are not considered numeric
260 // here.
Matt Spinler848799f2021-07-01 12:43:07 -0600261 if (!checked && (group.getMembers().size() > 1))
262 {
263 std::visit(
264 [&group, this](auto&& val) {
Patrick Williams61b73292023-05-10 07:50:12 -0500265 using V = std::decay_t<decltype(val)>;
266 if constexpr (!std::is_same_v<double, V> &&
267 !std::is_same_v<int32_t, V> &&
268 !std::is_same_v<int64_t, V>)
269 {
270 throw std::runtime_error{fmt::format(
271 "{}: Group {} has more than one member but "
272 "isn't numeric",
273 ActionBase::getName(), group.getName())};
274 }
Matt Spinler848799f2021-07-01 12:43:07 -0600275 },
276 value);
277 checked = true;
278 }
279
280 if (max && (value > max))
281 {
282 max = value;
283 }
284 else if (!max)
285 {
286 max = value;
287 }
288 }
289 catch (const std::out_of_range& e)
290 {
291 // Property not there, continue on
292 }
293 }
294
295 if (max)
296 {
297 tryConvertToDouble(*max);
298 }
299
300 return max;
301}
302
Matt Spinler1a555602022-12-08 13:19:35 -0600303bool MappedFloor::meetsCondition()
304{
305 if (!_conditionGroup)
306 {
307 return true;
308 }
309
310 bool meets = false;
311
312 // setCondition() also checks these
313 assert(_conditionGroup->getMembers().size() == 1);
314 assert((_conditionOp == "equal") || (_conditionOp == "not_equal"));
315
316 const auto& member = _conditionGroup->getMembers()[0];
317
318 try
319 {
320 auto value =
321 Manager::getObjValueVariant(member, _conditionGroup->getInterface(),
322 _conditionGroup->getProperty());
323
324 if ((_conditionOp == "equal") && (value == _conditionValue))
325 {
326 meets = true;
327 }
328 else if ((_conditionOp == "not_equal") && (value != _conditionValue))
329 {
330 meets = true;
331 }
332 }
333 catch (const std::out_of_range& e)
334 {
335 // Property not there, so consider it failing the 'equal'
336 // condition and passing the 'not_equal' condition.
337 if (_conditionOp == "equal")
338 {
339 meets = false;
340 }
341 else // not_equal
342 {
343 meets = true;
344 }
345 }
346
347 return meets;
348}
349
Matt Spinler848799f2021-07-01 12:43:07 -0600350void MappedFloor::run(Zone& zone)
351{
Matt Spinler1a555602022-12-08 13:19:35 -0600352 if (!meetsCondition())
353 {
354 // Make sure this no longer has a floor hold
355 if (zone.hasFloorHold(getUniqueName()))
356 {
357 zone.setFloorHold(getUniqueName(), 0, false);
358 }
359 return;
360 }
361
Matt Spinler848799f2021-07-01 12:43:07 -0600362 std::optional<uint64_t> newFloor;
Matt Spinler848799f2021-07-01 12:43:07 -0600363
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400364 auto keyValue = getMaxGroupValue(*_keyGroup);
Matt Spinler848799f2021-07-01 12:43:07 -0600365 if (!keyValue)
366 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600367 auto floor = _defaultFloor ? *_defaultFloor : zone.getDefaultFloor();
368 zone.setFloorHold(getUniqueName(), floor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600369 return;
370 }
371
372 for (const auto& floorTable : _fanFloors)
373 {
374 // First, find the floorTable entry to use based on the key value.
375 auto tableKeyValue = floorTable.keyValue;
376
377 // Convert numeric values from the JSON to doubles so they can
378 // be compared to values coming from D-Bus.
379 tryConvertToDouble(tableKeyValue);
380
381 // The key value from D-Bus must be less than the value
382 // in the table for this entry to be valid.
383 if (*keyValue >= tableKeyValue)
384 {
385 continue;
386 }
387
388 // Now check each group in the tables
Matt Spinlerc981bb52021-09-21 08:31:14 -0500389 for (const auto& [groupOrParameter, floorGroups] :
390 floorTable.floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600391 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500392 std::optional<PropertyVariantType> propertyValue;
393
394 if (std::holds_alternative<std::string>(groupOrParameter))
395 {
396 propertyValue = Manager::getParameter(
397 std::get<std::string>(groupOrParameter));
398 if (propertyValue)
399 {
400 tryConvertToDouble(*propertyValue);
401 }
402 else
403 {
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600404 // If the parameter isn't there, then don't use
405 // this floor table
406 log<level::DEBUG>(
Matt Spinlerc981bb52021-09-21 08:31:14 -0500407 fmt::format("{}: Parameter {} specified in the JSON "
408 "could not be found",
409 ActionBase::getName(),
410 std::get<std::string>(groupOrParameter))
411 .c_str());
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600412 continue;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500413 }
414 }
415 else
416 {
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400417 propertyValue =
418 getMaxGroupValue(*std::get<const Group*>(groupOrParameter));
Matt Spinlerc981bb52021-09-21 08:31:14 -0500419 }
420
Matt Spinler848799f2021-07-01 12:43:07 -0600421 std::optional<uint64_t> floor;
Matt Spinler76ef2012022-02-03 16:11:38 -0600422 if (propertyValue)
Matt Spinler848799f2021-07-01 12:43:07 -0600423 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600424 // Do either a <= or an == check depending on the data type
425 // to get the floor value based on this group.
426 for (const auto& [tableValue, tableFloor] : floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600427 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600428 PropertyVariantType value{tableValue};
429 tryConvertToDouble(value);
430
431 if (std::holds_alternative<double>(*propertyValue))
432 {
433 if (*propertyValue <= value)
434 {
435 floor = tableFloor;
436 break;
437 }
438 }
439 else if (*propertyValue == value)
Matt Spinler848799f2021-07-01 12:43:07 -0600440 {
441 floor = tableFloor;
442 break;
443 }
444 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600445 }
446
447 // No floor found in this group, use a default floor for now but
448 // let keep going in case it finds a higher one.
449 if (!floor)
450 {
451 if (floorTable.defaultFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600452 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600453 floor = *floorTable.defaultFloor;
454 }
455 else if (_defaultFloor)
456 {
457 floor = *_defaultFloor;
458 }
459 else
460 {
461 floor = zone.getDefaultFloor();
Matt Spinler848799f2021-07-01 12:43:07 -0600462 }
463 }
464
465 // Keep track of the highest floor value found across all
466 // entries/groups
Matt Spinler76ef2012022-02-03 16:11:38 -0600467 if ((newFloor && (floor > *newFloor)) || !newFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600468 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600469 newFloor = floor;
Matt Spinler848799f2021-07-01 12:43:07 -0600470 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600471 }
472
473 // if still no floor, use the default one from the floor table if
474 // there
475 if (!newFloor && floorTable.defaultFloor)
476 {
477 newFloor = floorTable.defaultFloor.value();
Matt Spinler848799f2021-07-01 12:43:07 -0600478 }
479
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600480 if (newFloor)
481 {
482 *newFloor = applyFloorOffset(*newFloor, floorTable.offsetParameter);
483 }
484
Matt Spinler848799f2021-07-01 12:43:07 -0600485 // Valid key value for this entry, so done
486 break;
487 }
488
Matt Spinler76ef2012022-02-03 16:11:38 -0600489 if (!newFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600490 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600491 newFloor = _defaultFloor ? *_defaultFloor : zone.getDefaultFloor();
Matt Spinler848799f2021-07-01 12:43:07 -0600492 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600493
494 zone.setFloorHold(getUniqueName(), *newFloor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600495}
496
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600497uint64_t MappedFloor::applyFloorOffset(uint64_t floor,
498 const std::string& offsetParameter) const
499{
500 if (!offsetParameter.empty())
501 {
502 auto offset = Manager::getParameter(offsetParameter);
503 if (offset)
504 {
505 if (std::holds_alternative<int32_t>(*offset))
506 {
507 return addFloorOffset(floor, std::get<int32_t>(*offset),
508 getUniqueName());
509 }
510 else if (std::holds_alternative<int64_t>(*offset))
511 {
512 return addFloorOffset(floor, std::get<int64_t>(*offset),
513 getUniqueName());
514 }
515 else if (std::holds_alternative<double>(*offset))
516 {
517 return addFloorOffset(floor, std::get<double>(*offset),
518 getUniqueName());
519 }
520 else
521 {
522 throw std::runtime_error(
523 "Invalid data type in floor offset parameter ");
524 }
525 }
526 }
527
528 return floor;
529}
530
Matt Spinler848799f2021-07-01 12:43:07 -0600531} // namespace phosphor::fan::control::json