blob: c2b78c3fbe107b8c64c7c36aaac1443e04bd1c7c [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
34MappedFloor::MappedFloor(const json& jsonObj,
35 const std::vector<Group>& groups) :
36 ActionBase(jsonObj, groups)
37{
38 setKeyGroup(jsonObj);
39 setFloorTable(jsonObj);
40}
41
42const Group* MappedFloor::getGroup(const std::string& name)
43{
44 auto groupIt =
45 find_if(_groups.begin(), _groups.end(),
46 [name](const auto& group) { return name == group.getName(); });
47
48 if (groupIt == _groups.end())
49 {
50 throw ActionParseError{
51 ActionBase::getName(),
52 fmt::format("Group name {} is not a valid group", name)};
53 }
54
55 return &(*groupIt);
56}
57
58void MappedFloor::setKeyGroup(const json& jsonObj)
59{
60 if (!jsonObj.contains("key_group"))
61 {
62 throw ActionParseError{ActionBase::getName(),
63 "Missing required 'key_group' entry"};
64 }
65 _keyGroup = getGroup(jsonObj["key_group"].get<std::string>());
66}
67
68void MappedFloor::setFloorTable(const json& jsonObj)
69{
70 if (!jsonObj.contains("fan_floors"))
71 {
72 throw ActionParseError{ActionBase::getName(),
73 "Missing fan_floors JSON entry"};
74 }
75
76 const auto& fanFloors = jsonObj.at("fan_floors");
77
78 for (const auto& floors : fanFloors)
79 {
80 if (!floors.contains("key") || !floors.contains("floors"))
81 {
82 throw ActionParseError{
83 ActionBase::getName(),
84 "Missing key or floors entries in actions/fan_floors JSON"};
85 }
86
87 FanFloors ff;
88 ff.keyValue = getJsonValue(floors["key"]);
89
90 for (const auto& groupEntry : floors["floors"])
91 {
Matt Spinlerc981bb52021-09-21 08:31:14 -050092 if ((!groupEntry.contains("group") &&
93 !groupEntry.contains("parameter")) ||
94 !groupEntry.contains("floors"))
Matt Spinler848799f2021-07-01 12:43:07 -060095 {
Matt Spinlerc981bb52021-09-21 08:31:14 -050096 throw ActionParseError{
97 ActionBase::getName(),
98 "Missing group, parameter, or floors entries in "
99 "actions/fan_floors/floors JSON"};
Matt Spinler848799f2021-07-01 12:43:07 -0600100 }
101
102 FloorGroup fg;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500103 if (groupEntry.contains("group"))
104 {
105 fg.groupOrParameter =
106 getGroup(groupEntry["group"].get<std::string>());
107 }
108 else
109 {
110 fg.groupOrParameter =
111 groupEntry["parameter"].get<std::string>();
112 }
Matt Spinler848799f2021-07-01 12:43:07 -0600113
114 for (const auto& floorEntry : groupEntry["floors"])
115 {
116 if (!floorEntry.contains("value") ||
117 !floorEntry.contains("floor"))
118 {
119
120 throw ActionParseError{
121 ActionBase::getName(),
122 "Missing value or floor entries in "
123 "actions/fan_floors/floors/floors JSON"};
124 }
125
126 auto value = getJsonValue(floorEntry["value"]);
127 auto floor = floorEntry["floor"].get<uint64_t>();
128
129 fg.floorEntries.emplace_back(std::move(value),
130 std::move(floor));
131 }
132
133 ff.floorGroups.push_back(std::move(fg));
134 }
135
136 _fanFloors.push_back(std::move(ff));
137 }
138}
139
140/**
141 * @brief Converts the variant to a double if it's a
142 * int32_t or int64_t.
143 */
144void tryConvertToDouble(PropertyVariantType& value)
145{
146 std::visit(
147 [&value](auto&& val) {
148 using V = std::decay_t<decltype(val)>;
149 if constexpr (std::is_same_v<int32_t, V> ||
150 std::is_same_v<int64_t, V>)
151 {
152 value = static_cast<double>(val);
153 }
154 },
155 value);
156}
157
158std::optional<PropertyVariantType>
159 MappedFloor::getMaxGroupValue(const Group& group, const Manager& manager)
160{
161 std::optional<PropertyVariantType> max;
162 bool checked = false;
163
164 for (const auto& member : group.getMembers())
165 {
166 try
167 {
168 auto value = Manager::getObjValueVariant(
169 member, group.getInterface(), group.getProperty());
170
171 // Only allow a group to have multiple members if it's numeric.
172 // Unlike std::is_arithmetic, bools are not considered numeric here.
173 if (!checked && (group.getMembers().size() > 1))
174 {
175 std::visit(
176 [&group, this](auto&& val) {
177 using V = std::decay_t<decltype(val)>;
178 if constexpr (!std::is_same_v<double, V> &&
179 !std::is_same_v<int32_t, V> &&
180 !std::is_same_v<int64_t, V>)
181 {
182 throw std::runtime_error{fmt::format(
183 "{}: Group {} has more than one member but "
184 "isn't numeric",
185 ActionBase::getName(), group.getName())};
186 }
187 },
188 value);
189 checked = true;
190 }
191
192 if (max && (value > max))
193 {
194 max = value;
195 }
196 else if (!max)
197 {
198 max = value;
199 }
200 }
201 catch (const std::out_of_range& e)
202 {
203 // Property not there, continue on
204 }
205 }
206
207 if (max)
208 {
209 tryConvertToDouble(*max);
210 }
211
212 return max;
213}
214
215void MappedFloor::run(Zone& zone)
216{
217 std::optional<uint64_t> newFloor;
218 bool missingGroupProperty = false;
219 auto& manager = *zone.getManager();
220
221 auto keyValue = getMaxGroupValue(*_keyGroup, manager);
222 if (!keyValue)
223 {
224 zone.setFloor(zone.getDefaultFloor());
225 return;
226 }
227
228 for (const auto& floorTable : _fanFloors)
229 {
230 // First, find the floorTable entry to use based on the key value.
231 auto tableKeyValue = floorTable.keyValue;
232
233 // Convert numeric values from the JSON to doubles so they can
234 // be compared to values coming from D-Bus.
235 tryConvertToDouble(tableKeyValue);
236
237 // The key value from D-Bus must be less than the value
238 // in the table for this entry to be valid.
239 if (*keyValue >= tableKeyValue)
240 {
241 continue;
242 }
243
244 // Now check each group in the tables
Matt Spinlerc981bb52021-09-21 08:31:14 -0500245 for (const auto& [groupOrParameter, floorGroups] :
246 floorTable.floorGroups)
Matt Spinler848799f2021-07-01 12:43:07 -0600247 {
Matt Spinlerc981bb52021-09-21 08:31:14 -0500248 std::optional<PropertyVariantType> propertyValue;
249
250 if (std::holds_alternative<std::string>(groupOrParameter))
251 {
252 propertyValue = Manager::getParameter(
253 std::get<std::string>(groupOrParameter));
254 if (propertyValue)
255 {
256 tryConvertToDouble(*propertyValue);
257 }
258 else
259 {
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600260 // If the parameter isn't there, then don't use
261 // this floor table
262 log<level::DEBUG>(
Matt Spinlerc981bb52021-09-21 08:31:14 -0500263 fmt::format("{}: Parameter {} specified in the JSON "
264 "could not be found",
265 ActionBase::getName(),
266 std::get<std::string>(groupOrParameter))
267 .c_str());
Matt Spinlerf0723ee2021-11-08 16:20:25 -0600268 continue;
Matt Spinlerc981bb52021-09-21 08:31:14 -0500269 }
270 }
271 else
272 {
273 propertyValue = getMaxGroupValue(
274 *std::get<const Group*>(groupOrParameter), manager);
275 }
276
Matt Spinler848799f2021-07-01 12:43:07 -0600277 if (!propertyValue)
278 {
279 // Couldn't successfully get a value. Results in default floor.
280 missingGroupProperty = true;
281 break;
282 }
283
284 // Do either a <= or an == check depending on the data type to get
285 // the floor value based on this group.
286 std::optional<uint64_t> floor;
287 for (const auto& [tableValue, tableFloor] : floorGroups)
288 {
289 PropertyVariantType value{tableValue};
290 tryConvertToDouble(value);
291
292 if (std::holds_alternative<double>(*propertyValue))
293 {
294 if (*propertyValue <= value)
295 {
296 floor = tableFloor;
297 break;
298 }
299 }
300 else if (*propertyValue == value)
301 {
302 floor = tableFloor;
303 break;
304 }
305 }
306
307 // Keep track of the highest floor value found across all
308 // entries/groups
309 if (floor)
310 {
311 if ((newFloor && (floor > *newFloor)) || !newFloor)
312 {
313 newFloor = floor;
314 }
315 }
316 else
317 {
318 // No match found in this group's table.
319 // Results in default floor.
320 missingGroupProperty = true;
321 }
322 }
323
324 // Valid key value for this entry, so done
325 break;
326 }
327
328 if (newFloor && !missingGroupProperty)
329 {
Matt Spinlerb8848652021-10-26 15:47:12 -0500330 zone.setFloorHold(getUniqueName(), *newFloor, true);
Matt Spinler848799f2021-07-01 12:43:07 -0600331 }
332 else
333 {
Matt Spinlerb8848652021-10-26 15:47:12 -0500334 zone.setFloorHold(getUniqueName(), zone.getDefaultFloor(), true);
Matt Spinler848799f2021-07-01 12:43:07 -0600335 }
336}
337
338} // namespace phosphor::fan::control::json