blob: 34cbfac320c26c1cff7a53b5553ec223c16e9d3a [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>
200 MappedFloor::getMaxGroupValue(const Group& group, const Manager& manager)
201{
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 auto& manager = *zone.getManager();
261
262 auto keyValue = getMaxGroupValue(*_keyGroup, manager);
263 if (!keyValue)
264 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600265 auto floor = _defaultFloor ? *_defaultFloor : zone.getDefaultFloor();
266 zone.setFloorHold(getUniqueName(), floor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600267 return;
268 }
269
270 for (const auto& floorTable : _fanFloors)
271 {
272 // First, find the floorTable entry to use based on the key value.
273 auto tableKeyValue = floorTable.keyValue;
274
275 // Convert numeric values from the JSON to doubles so they can
276 // be compared to values coming from D-Bus.
277 tryConvertToDouble(tableKeyValue);
278
279 // The key value from D-Bus must be less than the value
280 // in the table for this entry to be valid.
281 if (*keyValue >= tableKeyValue)
282 {
283 continue;
284 }
285
286 // Now check each group in the tables
Matt Spinlerc981bb52021-09-21 08:31:14 -0500287 for (const auto& [groupOrParameter, floorGroups] :
288 floorTable.floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600289 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500290 std::optional<PropertyVariantType> propertyValue;
291
292 if (std::holds_alternative<std::string>(groupOrParameter))
293 {
294 propertyValue = Manager::getParameter(
295 std::get<std::string>(groupOrParameter));
296 if (propertyValue)
297 {
298 tryConvertToDouble(*propertyValue);
299 }
300 else
301 {
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600302 // If the parameter isn't there, then don't use
303 // this floor table
304 log<level::DEBUG>(
Matt Spinlerc981bb52021-09-21 08:31:14 -0500305 fmt::format("{}: Parameter {} specified in the JSON "
306 "could not be found",
307 ActionBase::getName(),
308 std::get<std::string>(groupOrParameter))
309 .c_str());
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600310 continue;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500311 }
312 }
313 else
314 {
315 propertyValue = getMaxGroupValue(
316 *std::get<const Group*>(groupOrParameter), manager);
317 }
318
Matt Spinler848799f2021-07-01 12:43:07 -0600319 std::optional<uint64_t> floor;
Matt Spinler76ef2012022-02-03 16:11:38 -0600320 if (propertyValue)
Matt Spinler848799f2021-07-01 12:43:07 -0600321 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600322 // Do either a <= or an == check depending on the data type
323 // to get the floor value based on this group.
324 for (const auto& [tableValue, tableFloor] : floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600325 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600326 PropertyVariantType value{tableValue};
327 tryConvertToDouble(value);
328
329 if (std::holds_alternative<double>(*propertyValue))
330 {
331 if (*propertyValue <= value)
332 {
333 floor = tableFloor;
334 break;
335 }
336 }
337 else if (*propertyValue == value)
Matt Spinler848799f2021-07-01 12:43:07 -0600338 {
339 floor = tableFloor;
340 break;
341 }
342 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600343 }
344
345 // No floor found in this group, use a default floor for now but
346 // let keep going in case it finds a higher one.
347 if (!floor)
348 {
349 if (floorTable.defaultFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600350 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600351 floor = *floorTable.defaultFloor;
352 }
353 else if (_defaultFloor)
354 {
355 floor = *_defaultFloor;
356 }
357 else
358 {
359 floor = zone.getDefaultFloor();
Matt Spinler848799f2021-07-01 12:43:07 -0600360 }
361 }
362
363 // Keep track of the highest floor value found across all
364 // entries/groups
Matt Spinler76ef2012022-02-03 16:11:38 -0600365 if ((newFloor && (floor > *newFloor)) || !newFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600366 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600367 newFloor = floor;
Matt Spinler848799f2021-07-01 12:43:07 -0600368 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600369 }
370
371 // if still no floor, use the default one from the floor table if
372 // there
373 if (!newFloor && floorTable.defaultFloor)
374 {
375 newFloor = floorTable.defaultFloor.value();
Matt Spinler848799f2021-07-01 12:43:07 -0600376 }
377
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600378 if (newFloor)
379 {
380 *newFloor = applyFloorOffset(*newFloor, floorTable.offsetParameter);
381 }
382
Matt Spinler848799f2021-07-01 12:43:07 -0600383 // Valid key value for this entry, so done
384 break;
385 }
386
Matt Spinler76ef2012022-02-03 16:11:38 -0600387 if (!newFloor)
Matt Spinler848799f2021-07-01 12:43:07 -0600388 {
Matt Spinler76ef2012022-02-03 16:11:38 -0600389 newFloor = _defaultFloor ? *_defaultFloor : zone.getDefaultFloor();
Matt Spinler848799f2021-07-01 12:43:07 -0600390 }
Matt Spinler76ef2012022-02-03 16:11:38 -0600391
392 zone.setFloorHold(getUniqueName(), *newFloor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600393}
394
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600395uint64_t MappedFloor::applyFloorOffset(uint64_t floor,
396 const std::string& offsetParameter) const
397{
398 if (!offsetParameter.empty())
399 {
400 auto offset = Manager::getParameter(offsetParameter);
401 if (offset)
402 {
403 if (std::holds_alternative<int32_t>(*offset))
404 {
405 return addFloorOffset(floor, std::get<int32_t>(*offset),
406 getUniqueName());
407 }
408 else if (std::holds_alternative<int64_t>(*offset))
409 {
410 return addFloorOffset(floor, std::get<int64_t>(*offset),
411 getUniqueName());
412 }
413 else if (std::holds_alternative<double>(*offset))
414 {
415 return addFloorOffset(floor, std::get<double>(*offset),
416 getUniqueName());
417 }
418 else
419 {
420 throw std::runtime_error(
421 "Invalid data type in floor offset parameter ");
422 }
423 }
424 }
425
426 return floor;
427}
428
Matt Spinler848799f2021-07-01 12:43:07 -0600429} // namespace phosphor::fan::control::json