blob: 906559b1b0eab3ba09eca2bcbab2b1027a7168cc [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 {
161
162 throw ActionParseError{
163 ActionBase::getName(),
164 "Missing value or floor entries in "
165 "actions/fan_floors/floors/floors JSON"};
166 }
167
168 auto value = getJsonValue(floorEntry["value"]);
169 auto floor = floorEntry["floor"].get<uint64_t>();
170
171 fg.floorEntries.emplace_back(std::move(value),
172 std::move(floor));
173 }
174
175 ff.floorGroups.push_back(std::move(fg));
176 }
177
178 _fanFloors.push_back(std::move(ff));
179 }
180}
181
Matt Spinler1a555602022-12-08 13:19:35 -0600182void MappedFloor::setCondition(const json& jsonObj)
183{
184 // condition_group, condition_value, and condition_op
185 // are optional, though they must show up together.
186 // Assume if condition_group is present then they all
187 // must be.
188 if (!jsonObj.contains("condition_group"))
189 {
190 return;
191 }
192
193 _conditionGroup = getGroup(jsonObj["condition_group"].get<std::string>());
194
195 if (_conditionGroup->getMembers().size() != 1)
196 {
197 throw ActionParseError{
198 ActionBase::getName(),
199 fmt::format("condition_group {} must only have 1 member",
200 _conditionGroup->getName())};
201 }
202
203 if (!jsonObj.contains("condition_value"))
204 {
205 throw ActionParseError{ActionBase::getName(),
206 "Missing required 'condition_value' entry in "
207 "mapped_floor action"};
208 }
209
210 _conditionValue = getJsonValue(jsonObj["condition_value"]);
211
212 if (!jsonObj.contains("condition_op"))
213 {
214 throw ActionParseError{ActionBase::getName(),
215 "Missing required 'condition_op' entry in "
216 "mapped_floor action"};
217 }
218
219 _conditionOp = jsonObj["condition_op"].get<std::string>();
220
221 if ((_conditionOp != "equal") && (_conditionOp != "not_equal"))
222 {
223 throw ActionParseError{ActionBase::getName(),
224 "Invalid 'condition_op' value in "
225 "mapped_floor action"};
226 }
227}
228
Matt Spinler848799f2021-07-01 12:43:07 -0600229/**
230 * @brief Converts the variant to a double if it's a
231 * int32_t or int64_t.
232 */
233void tryConvertToDouble(PropertyVariantType& value)
234{
235 std::visit(
236 [&value](auto&& val) {
237 using V = std::decay_t<decltype(val)>;
238 if constexpr (std::is_same_v<int32_t, V> ||
239 std::is_same_v<int64_t, V>)
240 {
241 value = static_cast<double>(val);
242 }
243 },
244 value);
245}
246
247std::optional<PropertyVariantType>
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400248 MappedFloor::getMaxGroupValue(const Group& group)
Matt Spinler848799f2021-07-01 12:43:07 -0600249{
250 std::optional<PropertyVariantType> max;
251 bool checked = false;
252
253 for (const auto& member : group.getMembers())
254 {
255 try
256 {
257 auto value = Manager::getObjValueVariant(
258 member, group.getInterface(), group.getProperty());
259
260 // Only allow a group to have multiple members if it's numeric.
Matt Spinler76ef2012022-02-03 16:11:38 -0600261 // Unlike std::is_arithmetic, bools are not considered numeric
262 // here.
Matt Spinler848799f2021-07-01 12:43:07 -0600263 if (!checked && (group.getMembers().size() > 1))
264 {
265 std::visit(
266 [&group, this](auto&& val) {
267 using V = std::decay_t<decltype(val)>;
268 if constexpr (!std::is_same_v<double, V> &&
269 !std::is_same_v<int32_t, V> &&
270 !std::is_same_v<int64_t, V>)
271 {
272 throw std::runtime_error{fmt::format(
273 "{}: Group {} has more than one member but "
274 "isn't numeric",
275 ActionBase::getName(), group.getName())};
276 }
277 },
278 value);
279 checked = true;
280 }
281
282 if (max && (value > max))
283 {
284 max = value;
285 }
286 else if (!max)
287 {
288 max = value;
289 }
290 }
291 catch (const std::out_of_range& e)
292 {
293 // Property not there, continue on
294 }
295 }
296
297 if (max)
298 {
299 tryConvertToDouble(*max);
300 }
301
302 return max;
303}
304
Matt Spinler1a555602022-12-08 13:19:35 -0600305bool MappedFloor::meetsCondition()
306{
307 if (!_conditionGroup)
308 {
309 return true;
310 }
311
312 bool meets = false;
313
314 // setCondition() also checks these
315 assert(_conditionGroup->getMembers().size() == 1);
316 assert((_conditionOp == "equal") || (_conditionOp == "not_equal"));
317
318 const auto& member = _conditionGroup->getMembers()[0];
319
320 try
321 {
322 auto value =
323 Manager::getObjValueVariant(member, _conditionGroup->getInterface(),
324 _conditionGroup->getProperty());
325
326 if ((_conditionOp == "equal") && (value == _conditionValue))
327 {
328 meets = true;
329 }
330 else if ((_conditionOp == "not_equal") && (value != _conditionValue))
331 {
332 meets = true;
333 }
334 }
335 catch (const std::out_of_range& e)
336 {
337 // Property not there, so consider it failing the 'equal'
338 // condition and passing the 'not_equal' condition.
339 if (_conditionOp == "equal")
340 {
341 meets = false;
342 }
343 else // not_equal
344 {
345 meets = true;
346 }
347 }
348
349 return meets;
350}
351
Matt Spinler848799f2021-07-01 12:43:07 -0600352void MappedFloor::run(Zone& zone)
353{
Matt Spinler1a555602022-12-08 13:19:35 -0600354 if (!meetsCondition())
355 {
356 // Make sure this no longer has a floor hold
357 if (zone.hasFloorHold(getUniqueName()))
358 {
359 zone.setFloorHold(getUniqueName(), 0, false);
360 }
361 return;
362 }
363
Matt Spinler848799f2021-07-01 12:43:07 -0600364 std::optional<uint64_t> newFloor;
Matt Spinler848799f2021-07-01 12:43:07 -0600365
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400366 auto keyValue = getMaxGroupValue(*_keyGroup);
Matt Spinler848799f2021-07-01 12:43:07 -0600367 if (!keyValue)
368 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600369 auto floor = _defaultFloor ? *_defaultFloor : zone.getDefaultFloor();
370 zone.setFloorHold(getUniqueName(), floor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600371 return;
372 }
373
374 for (const auto& floorTable : _fanFloors)
375 {
376 // First, find the floorTable entry to use based on the key value.
377 auto tableKeyValue = floorTable.keyValue;
378
379 // Convert numeric values from the JSON to doubles so they can
380 // be compared to values coming from D-Bus.
381 tryConvertToDouble(tableKeyValue);
382
383 // The key value from D-Bus must be less than the value
384 // in the table for this entry to be valid.
385 if (*keyValue >= tableKeyValue)
386 {
387 continue;
388 }
389
390 // Now check each group in the tables
Matt Spinlerc981bb52021-09-21 08:31:14 -0500391 for (const auto& [groupOrParameter, floorGroups] :
392 floorTable.floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600393 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500394 std::optional<PropertyVariantType> propertyValue;
395
396 if (std::holds_alternative<std::string>(groupOrParameter))
397 {
398 propertyValue = Manager::getParameter(
399 std::get<std::string>(groupOrParameter));
400 if (propertyValue)
401 {
402 tryConvertToDouble(*propertyValue);
403 }
404 else
405 {
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600406 // If the parameter isn't there, then don't use
407 // this floor table
408 log<level::DEBUG>(
Matt Spinlerc981bb52021-09-21 08:31:14 -0500409 fmt::format("{}: Parameter {} specified in the JSON "
410 "could not be found",
411 ActionBase::getName(),
412 std::get<std::string>(groupOrParameter))
413 .c_str());
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600414 continue;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500415 }
416 }
417 else
418 {
Mike Cappsb2e9a4f2022-06-13 10:15:42 -0400419 propertyValue =
420 getMaxGroupValue(*std::get<const Group*>(groupOrParameter));
Matt Spinlerc981bb52021-09-21 08:31:14 -0500421 }
422
Matt Spinler848799f2021-07-01 12:43:07 -0600423 std::optional<uint64_t> floor;
Matt Spinler76ef2012022-02-03 16:11:38 -0600424 if (propertyValue)
Matt Spinler848799f2021-07-01 12:43:07 -0600425 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600426 // Do either a <= or an == check depending on the data type
427 // to get the floor value based on this group.
428 for (const auto& [tableValue, tableFloor] : floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600429 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600430 PropertyVariantType value{tableValue};
431 tryConvertToDouble(value);
432
433 if (std::holds_alternative<double>(*propertyValue))
434 {
435 if (*propertyValue <= value)
436 {
437 floor = tableFloor;
438 break;
439 }
440 }
441 else if (*propertyValue == value)
Matt Spinler848799f2021-07-01 12:43:07 -0600442 {
443 floor = tableFloor;
444 break;
445 }
446 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600447 }
448
449 // No floor found in this group, use a default floor for now but
450 // let keep going in case it finds a higher one.
451 if (!floor)
452 {
453 if (floorTable.defaultFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600454 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600455 floor = *floorTable.defaultFloor;
456 }
457 else if (_defaultFloor)
458 {
459 floor = *_defaultFloor;
460 }
461 else
462 {
463 floor = zone.getDefaultFloor();
Matt Spinler848799f2021-07-01 12:43:07 -0600464 }
465 }
466
467 // Keep track of the highest floor value found across all
468 // entries/groups
Matt Spinler76ef2012022-02-03 16:11:38 -0600469 if ((newFloor && (floor > *newFloor)) || !newFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600470 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600471 newFloor = floor;
Matt Spinler848799f2021-07-01 12:43:07 -0600472 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600473 }
474
475 // if still no floor, use the default one from the floor table if
476 // there
477 if (!newFloor && floorTable.defaultFloor)
478 {
479 newFloor = floorTable.defaultFloor.value();
Matt Spinler848799f2021-07-01 12:43:07 -0600480 }
481
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600482 if (newFloor)
483 {
484 *newFloor = applyFloorOffset(*newFloor, floorTable.offsetParameter);
485 }
486
Matt Spinler848799f2021-07-01 12:43:07 -0600487 // Valid key value for this entry, so done
488 break;
489 }
490
Matt Spinler76ef2012022-02-03 16:11:38 -0600491 if (!newFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600492 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600493 newFloor = _defaultFloor ? *_defaultFloor : zone.getDefaultFloor();
Matt Spinler848799f2021-07-01 12:43:07 -0600494 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600495
496 zone.setFloorHold(getUniqueName(), *newFloor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600497}
498
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600499uint64_t MappedFloor::applyFloorOffset(uint64_t floor,
500 const std::string& offsetParameter) const
501{
502 if (!offsetParameter.empty())
503 {
504 auto offset = Manager::getParameter(offsetParameter);
505 if (offset)
506 {
507 if (std::holds_alternative<int32_t>(*offset))
508 {
509 return addFloorOffset(floor, std::get<int32_t>(*offset),
510 getUniqueName());
511 }
512 else if (std::holds_alternative<int64_t>(*offset))
513 {
514 return addFloorOffset(floor, std::get<int64_t>(*offset),
515 getUniqueName());
516 }
517 else if (std::holds_alternative<double>(*offset))
518 {
519 return addFloorOffset(floor, std::get<double>(*offset),
520 getUniqueName());
521 }
522 else
523 {
524 throw std::runtime_error(
525 "Invalid data type in floor offset parameter ");
526 }
527 }
528 }
529
530 return floor;
531}
532
Matt Spinler848799f2021-07-01 12:43:07 -0600533} // namespace phosphor::fan::control::json