blob: b84de15733cdf49dec338a4113d362b94dbfe807 [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);
61}
62
63const Group* MappedFloor::getGroup(const std::string& name)
64{
65 auto groupIt =
66 find_if(_groups.begin(), _groups.end(),
67 [name](const auto& group) { return name == group.getName(); });
68
69 if (groupIt == _groups.end())
70 {
71 throw ActionParseError{
72 ActionBase::getName(),
73 fmt::format("Group name {} is not a valid group", name)};
74 }
75
76 return &(*groupIt);
77}
78
79void MappedFloor::setKeyGroup(const json& jsonObj)
80{
81 if (!jsonObj.contains("key_group"))
82 {
83 throw ActionParseError{ActionBase::getName(),
84 "Missing required 'key_group' entry"};
85 }
86 _keyGroup = getGroup(jsonObj["key_group"].get<std::string>());
87}
88
89void MappedFloor::setFloorTable(const json& jsonObj)
90{
91 if (!jsonObj.contains("fan_floors"))
92 {
93 throw ActionParseError{ActionBase::getName(),
94 "Missing fan_floors JSON entry"};
95 }
96
97 const auto& fanFloors = jsonObj.at("fan_floors");
98
99 for (const auto& floors : fanFloors)
100 {
101 if (!floors.contains("key") || !floors.contains("floors"))
102 {
103 throw ActionParseError{
104 ActionBase::getName(),
105 "Missing key or floors entries in actions/fan_floors JSON"};
106 }
107
108 FanFloors ff;
109 ff.keyValue = getJsonValue(floors["key"]);
110
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600111 if (floors.contains("floor_offset_parameter"))
112 {
113 ff.offsetParameter =
114 floors["floor_offset_parameter"].get<std::string>();
115 }
116
Matt Spinler848799f2021-07-01 12:43:07 -0600117 for (const auto& groupEntry : floors["floors"])
118 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500119 if ((!groupEntry.contains("group") &&
120 !groupEntry.contains("parameter")) ||
121 !groupEntry.contains("floors"))
Matt Spinler848799f2021-07-01 12:43:07 -0600122 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500123 throw ActionParseError{
124 ActionBase::getName(),
125 "Missing group, parameter, or floors entries in "
126 "actions/fan_floors/floors JSON"};
Matt Spinler848799f2021-07-01 12:43:07 -0600127 }
128
129 FloorGroup fg;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500130 if (groupEntry.contains("group"))
131 {
132 fg.groupOrParameter =
133 getGroup(groupEntry["group"].get<std::string>());
134 }
135 else
136 {
137 fg.groupOrParameter =
138 groupEntry["parameter"].get<std::string>();
139 }
Matt Spinler848799f2021-07-01 12:43:07 -0600140
141 for (const auto& floorEntry : groupEntry["floors"])
142 {
143 if (!floorEntry.contains("value") ||
144 !floorEntry.contains("floor"))
145 {
146
147 throw ActionParseError{
148 ActionBase::getName(),
149 "Missing value or floor entries in "
150 "actions/fan_floors/floors/floors JSON"};
151 }
152
153 auto value = getJsonValue(floorEntry["value"]);
154 auto floor = floorEntry["floor"].get<uint64_t>();
155
156 fg.floorEntries.emplace_back(std::move(value),
157 std::move(floor));
158 }
159
160 ff.floorGroups.push_back(std::move(fg));
161 }
162
163 _fanFloors.push_back(std::move(ff));
164 }
165}
166
167/**
168 * @brief Converts the variant to a double if it's a
169 * int32_t or int64_t.
170 */
171void tryConvertToDouble(PropertyVariantType& value)
172{
173 std::visit(
174 [&value](auto&& val) {
175 using V = std::decay_t<decltype(val)>;
176 if constexpr (std::is_same_v<int32_t, V> ||
177 std::is_same_v<int64_t, V>)
178 {
179 value = static_cast<double>(val);
180 }
181 },
182 value);
183}
184
185std::optional<PropertyVariantType>
186 MappedFloor::getMaxGroupValue(const Group& group, const Manager& manager)
187{
188 std::optional<PropertyVariantType> max;
189 bool checked = false;
190
191 for (const auto& member : group.getMembers())
192 {
193 try
194 {
195 auto value = Manager::getObjValueVariant(
196 member, group.getInterface(), group.getProperty());
197
198 // Only allow a group to have multiple members if it's numeric.
199 // Unlike std::is_arithmetic, bools are not considered numeric here.
200 if (!checked && (group.getMembers().size() > 1))
201 {
202 std::visit(
203 [&group, this](auto&& val) {
204 using V = std::decay_t<decltype(val)>;
205 if constexpr (!std::is_same_v<double, V> &&
206 !std::is_same_v<int32_t, V> &&
207 !std::is_same_v<int64_t, V>)
208 {
209 throw std::runtime_error{fmt::format(
210 "{}: Group {} has more than one member but "
211 "isn't numeric",
212 ActionBase::getName(), group.getName())};
213 }
214 },
215 value);
216 checked = true;
217 }
218
219 if (max && (value > max))
220 {
221 max = value;
222 }
223 else if (!max)
224 {
225 max = value;
226 }
227 }
228 catch (const std::out_of_range& e)
229 {
230 // Property not there, continue on
231 }
232 }
233
234 if (max)
235 {
236 tryConvertToDouble(*max);
237 }
238
239 return max;
240}
241
242void MappedFloor::run(Zone& zone)
243{
244 std::optional<uint64_t> newFloor;
245 bool missingGroupProperty = false;
246 auto& manager = *zone.getManager();
247
248 auto keyValue = getMaxGroupValue(*_keyGroup, manager);
249 if (!keyValue)
250 {
251 zone.setFloor(zone.getDefaultFloor());
252 return;
253 }
254
255 for (const auto& floorTable : _fanFloors)
256 {
257 // First, find the floorTable entry to use based on the key value.
258 auto tableKeyValue = floorTable.keyValue;
259
260 // Convert numeric values from the JSON to doubles so they can
261 // be compared to values coming from D-Bus.
262 tryConvertToDouble(tableKeyValue);
263
264 // The key value from D-Bus must be less than the value
265 // in the table for this entry to be valid.
266 if (*keyValue >= tableKeyValue)
267 {
268 continue;
269 }
270
271 // Now check each group in the tables
Matt Spinlerc981bb52021-09-21 08:31:14 -0500272 for (const auto& [groupOrParameter, floorGroups] :
273 floorTable.floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600274 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500275 std::optional<PropertyVariantType> propertyValue;
276
277 if (std::holds_alternative<std::string>(groupOrParameter))
278 {
279 propertyValue = Manager::getParameter(
280 std::get<std::string>(groupOrParameter));
281 if (propertyValue)
282 {
283 tryConvertToDouble(*propertyValue);
284 }
285 else
286 {
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600287 // If the parameter isn't there, then don't use
288 // this floor table
289 log<level::DEBUG>(
Matt Spinlerc981bb52021-09-21 08:31:14 -0500290 fmt::format("{}: Parameter {} specified in the JSON "
291 "could not be found",
292 ActionBase::getName(),
293 std::get<std::string>(groupOrParameter))
294 .c_str());
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600295 continue;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500296 }
297 }
298 else
299 {
300 propertyValue = getMaxGroupValue(
301 *std::get<const Group*>(groupOrParameter), manager);
302 }
303
Matt Spinler848799f2021-07-01 12:43:07 -0600304 if (!propertyValue)
305 {
306 // Couldn't successfully get a value. Results in default floor.
307 missingGroupProperty = true;
308 break;
309 }
310
311 // Do either a <= or an == check depending on the data type to get
312 // the floor value based on this group.
313 std::optional<uint64_t> floor;
314 for (const auto& [tableValue, tableFloor] : floorGroups)
315 {
316 PropertyVariantType value{tableValue};
317 tryConvertToDouble(value);
318
319 if (std::holds_alternative<double>(*propertyValue))
320 {
321 if (*propertyValue <= value)
322 {
323 floor = tableFloor;
324 break;
325 }
326 }
327 else if (*propertyValue == value)
328 {
329 floor = tableFloor;
330 break;
331 }
332 }
333
334 // Keep track of the highest floor value found across all
335 // entries/groups
336 if (floor)
337 {
338 if ((newFloor && (floor > *newFloor)) || !newFloor)
339 {
340 newFloor = floor;
341 }
342 }
343 else
344 {
345 // No match found in this group's table.
346 // Results in default floor.
347 missingGroupProperty = true;
348 }
349 }
350
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600351 if (newFloor)
352 {
353 *newFloor = applyFloorOffset(*newFloor, floorTable.offsetParameter);
354 }
355
Matt Spinler848799f2021-07-01 12:43:07 -0600356 // Valid key value for this entry, so done
357 break;
358 }
359
360 if (newFloor && !missingGroupProperty)
361 {
Matt Spinlerb8848652021-10-26 15:47:12 -0500362 zone.setFloorHold(getUniqueName(), *newFloor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600363 }
364 else
365 {
Matt Spinlerb8848652021-10-26 15:47:12 -0500366 zone.setFloorHold(getUniqueName(), zone.getDefaultFloor(), true);
Matt Spinler848799f2021-07-01 12:43:07 -0600367 }
368}
369
Matt Spinlera17d5cc2022-02-02 13:13:30 -0600370uint64_t MappedFloor::applyFloorOffset(uint64_t floor,
371 const std::string& offsetParameter) const
372{
373 if (!offsetParameter.empty())
374 {
375 auto offset = Manager::getParameter(offsetParameter);
376 if (offset)
377 {
378 if (std::holds_alternative<int32_t>(*offset))
379 {
380 return addFloorOffset(floor, std::get<int32_t>(*offset),
381 getUniqueName());
382 }
383 else if (std::holds_alternative<int64_t>(*offset))
384 {
385 return addFloorOffset(floor, std::get<int64_t>(*offset),
386 getUniqueName());
387 }
388 else if (std::holds_alternative<double>(*offset))
389 {
390 return addFloorOffset(floor, std::get<double>(*offset),
391 getUniqueName());
392 }
393 else
394 {
395 throw std::runtime_error(
396 "Invalid data type in floor offset parameter ");
397 }
398 }
399 }
400
401 return floor;
402}
403
Matt Spinler848799f2021-07-01 12:43:07 -0600404} // namespace phosphor::fan::control::json