blob: bca3bf5a3d4a73f4a98bbd71fc33418c5e470dfb [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 {
92 if (!groupEntry.contains("group") || !groupEntry.contains("floors"))
93 {
94 throw ActionParseError{ActionBase::getName(),
95 "Missing group or floors entries in "
96 "actions/fan_floors/floors JSON"};
97 }
98
99 FloorGroup fg;
100 fg.group = getGroup(groupEntry["group"].get<std::string>());
101
102 for (const auto& floorEntry : groupEntry["floors"])
103 {
104 if (!floorEntry.contains("value") ||
105 !floorEntry.contains("floor"))
106 {
107
108 throw ActionParseError{
109 ActionBase::getName(),
110 "Missing value or floor entries in "
111 "actions/fan_floors/floors/floors JSON"};
112 }
113
114 auto value = getJsonValue(floorEntry["value"]);
115 auto floor = floorEntry["floor"].get<uint64_t>();
116
117 fg.floorEntries.emplace_back(std::move(value),
118 std::move(floor));
119 }
120
121 ff.floorGroups.push_back(std::move(fg));
122 }
123
124 _fanFloors.push_back(std::move(ff));
125 }
126}
127
128/**
129 * @brief Converts the variant to a double if it's a
130 * int32_t or int64_t.
131 */
132void tryConvertToDouble(PropertyVariantType& value)
133{
134 std::visit(
135 [&value](auto&& val) {
136 using V = std::decay_t<decltype(val)>;
137 if constexpr (std::is_same_v<int32_t, V> ||
138 std::is_same_v<int64_t, V>)
139 {
140 value = static_cast<double>(val);
141 }
142 },
143 value);
144}
145
146std::optional<PropertyVariantType>
147 MappedFloor::getMaxGroupValue(const Group& group, const Manager& manager)
148{
149 std::optional<PropertyVariantType> max;
150 bool checked = false;
151
152 for (const auto& member : group.getMembers())
153 {
154 try
155 {
156 auto value = Manager::getObjValueVariant(
157 member, group.getInterface(), group.getProperty());
158
159 // Only allow a group to have multiple members if it's numeric.
160 // Unlike std::is_arithmetic, bools are not considered numeric here.
161 if (!checked && (group.getMembers().size() > 1))
162 {
163 std::visit(
164 [&group, this](auto&& val) {
165 using V = std::decay_t<decltype(val)>;
166 if constexpr (!std::is_same_v<double, V> &&
167 !std::is_same_v<int32_t, V> &&
168 !std::is_same_v<int64_t, V>)
169 {
170 throw std::runtime_error{fmt::format(
171 "{}: Group {} has more than one member but "
172 "isn't numeric",
173 ActionBase::getName(), group.getName())};
174 }
175 },
176 value);
177 checked = true;
178 }
179
180 if (max && (value > max))
181 {
182 max = value;
183 }
184 else if (!max)
185 {
186 max = value;
187 }
188 }
189 catch (const std::out_of_range& e)
190 {
191 // Property not there, continue on
192 }
193 }
194
195 if (max)
196 {
197 tryConvertToDouble(*max);
198 }
199
200 return max;
201}
202
203void MappedFloor::run(Zone& zone)
204{
205 std::optional<uint64_t> newFloor;
206 bool missingGroupProperty = false;
207 auto& manager = *zone.getManager();
208
209 auto keyValue = getMaxGroupValue(*_keyGroup, manager);
210 if (!keyValue)
211 {
212 zone.setFloor(zone.getDefaultFloor());
213 return;
214 }
215
216 for (const auto& floorTable : _fanFloors)
217 {
218 // First, find the floorTable entry to use based on the key value.
219 auto tableKeyValue = floorTable.keyValue;
220
221 // Convert numeric values from the JSON to doubles so they can
222 // be compared to values coming from D-Bus.
223 tryConvertToDouble(tableKeyValue);
224
225 // The key value from D-Bus must be less than the value
226 // in the table for this entry to be valid.
227 if (*keyValue >= tableKeyValue)
228 {
229 continue;
230 }
231
232 // Now check each group in the tables
233 for (const auto& [group, floorGroups] : floorTable.floorGroups)
234 {
235 auto propertyValue = getMaxGroupValue(*group, manager);
236 if (!propertyValue)
237 {
238 // Couldn't successfully get a value. Results in default floor.
239 missingGroupProperty = true;
240 break;
241 }
242
243 // Do either a <= or an == check depending on the data type to get
244 // the floor value based on this group.
245 std::optional<uint64_t> floor;
246 for (const auto& [tableValue, tableFloor] : floorGroups)
247 {
248 PropertyVariantType value{tableValue};
249 tryConvertToDouble(value);
250
251 if (std::holds_alternative<double>(*propertyValue))
252 {
253 if (*propertyValue <= value)
254 {
255 floor = tableFloor;
256 break;
257 }
258 }
259 else if (*propertyValue == value)
260 {
261 floor = tableFloor;
262 break;
263 }
264 }
265
266 // Keep track of the highest floor value found across all
267 // entries/groups
268 if (floor)
269 {
270 if ((newFloor && (floor > *newFloor)) || !newFloor)
271 {
272 newFloor = floor;
273 }
274 }
275 else
276 {
277 // No match found in this group's table.
278 // Results in default floor.
279 missingGroupProperty = true;
280 }
281 }
282
283 // Valid key value for this entry, so done
284 break;
285 }
286
287 if (newFloor && !missingGroupProperty)
288 {
289 zone.setFloor(*newFloor);
290 }
291 else
292 {
293 zone.setFloor(zone.getDefaultFloor());
294 }
295}
296
297} // namespace phosphor::fan::control::json