control: Create MappedFloor action
This action can be used to set a floor value based on 2 or more groups
having values within certain ranges, where the key group chooses the set
of tables in which to check the remaining group values.
For example, with the following JSON:
{
"name": "mapped_floor",
"key_group": "ambient_temp",
"fan_floors": [
{
"key": 27,
"floors": [
{
"group": "altitude",
"floors": [
{
"value": 5000,
"floor": 2000
},
{
"value": 7000,
"floor": 6000
}
]
},
{
"group": "power_mode",
"floors": [
{
"value": "PowerSave",
"floor": 3000
},
{
"value": "MaximumPerformance",
"floor": 5000
}
]
}
]
}
]
}
If the ambient_temp group has a value less than 27, then it looks up the
values for the altitude and power_mode groups, where for altitude, since
it's numeric, it will use a <= operator, and for power_mode, since it's
a string, it will use an == operator when comparing to the values in the
JSON. It will then choose the largest floor value between the altitude
and power_mode results.
There are several scenarios that result in a default floor being set.
Full action documentation is in the class header file.
Signed-off-by: Matt Spinler <spinler@us.ibm.com>
Change-Id: I8e0ff3a97ff73dd20018473c1993b2e043276099
diff --git a/control/json/actions/mapped_floor.hpp b/control/json/actions/mapped_floor.hpp
new file mode 100644
index 0000000..baf2701
--- /dev/null
+++ b/control/json/actions/mapped_floor.hpp
@@ -0,0 +1,194 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "../zone.hpp"
+#include "action.hpp"
+#include "group.hpp"
+
+#include <nlohmann/json.hpp>
+
+namespace phosphor::fan::control::json
+{
+
+using json = nlohmann::json;
+
+/**
+ * @class MappedFloor - Action to set a fan floor based on ranges of
+ * multiple sensor values.
+ * For example, consider the following config:
+ *
+ * {
+ * "name": "mapped_floor",
+ * "key_group": "ambient_temp",
+ * "fan_floors": [
+ * {
+ * "key": 27,
+ * "floors": [
+ * {
+ * "group": "altitude",
+ * "floors": [
+ * {
+ * "value": 5000,
+ * "floor": 4500
+ * }
+ * ]
+ * },
+ * {
+ * "group": "power_mode",
+ * "floors": [
+ * {
+ * "value": "MaximumPerformance",
+ * "floor": 5000
+ * }
+ * ]
+ * }
+ * ]
+ * }
+ * ]
+ * }
+ *
+ * When it runs, it will:
+ *
+ * 1. Evaluate the key_group
+ * - Find the max D-Bus property value (if numeric) of the member properties
+ * in this group.
+ * - Check it against each 'key' value in the fan_floor entries until
+ * the key_group property value < key value, so:
+ * max ambient temp < 27.
+ * - If the above check passes, the rest of that entry will be evaluated
+ * and then the action will be done.
+ *
+ * 2. Evaluate the group values in each floors array entry for this key value.
+ * - Find the max D-Bus property value (if numeric) of the member properties
+ * of this group - in this case 'altitude'.
+ * - Depending on the data type of that group, compare to the 'value' entry:
+ * - If numeric, check if the group's value is <= the 'value' one
+ * - Otherwise, check if it is ==
+ * - If that passes, save the value from the 'floor' entry and continue to
+ * the next entry in the floors array, which, if present, would specify
+ * another group to check. In this case, 'power_mode'. Repeat the above
+ * step.
+ * - After all the group compares are done, choose the largest floor value
+ * to set the fan floor to. If any group check results doesn't end in
+ * a match being found, then the default floor will be set.
+ *
+ * Cases where the default floor will be set:
+ * - A table entry can't be found based on a key group's value.
+ * - A table entry can't be found based on a group's value.
+ * - A value can't be obtained for the 'key_group' D-Bus property group.
+ * - A value can't be obtained for any of the 'group' property groups.
+ * - A value is NaN, as no <, <=, or == checks would succeed.
+ *
+ * Other notes:
+ * - If a group has multiple members, they must be numeric or else
+ * the code will throw an exception.
+ */
+
+class MappedFloor : public ActionBase, public ActionRegister<MappedFloor>
+{
+ public:
+ /* Name of this action */
+ static constexpr auto name = "mapped_floor";
+
+ MappedFloor() = delete;
+ MappedFloor(const MappedFloor&) = delete;
+ MappedFloor(MappedFloor&&) = delete;
+ MappedFloor& operator=(const MappedFloor&) = delete;
+ MappedFloor& operator=(MappedFloor&&) = delete;
+ ~MappedFloor() = default;
+
+ /**
+ * @brief Parse the JSON to set the members
+ *
+ * @param[in] jsonObj - JSON configuration of this action
+ * @param[in] groups - Groups of dbus objects the action uses
+ */
+ MappedFloor(const json& jsonObj, const std::vector<Group>& groups);
+
+ /**
+ * @brief Run the action. See description above.
+ *
+ * @param[in] zone - Zone to run the action on
+ */
+ void run(Zone& zone) override;
+
+ private:
+ /**
+ * @brief Parse and set the key group
+ *
+ * @param[in] jsonObj - JSON object for the action
+ */
+ void setKeyGroup(const json& jsonObj);
+
+ /**
+ * @brief Parses and sets the floor group data members
+ *
+ * @param[in] jsonObj - JSON object for the action
+ */
+ void setFloorTable(const json& jsonObj);
+
+ /**
+ * @brief Determines the maximum value of the property specified
+ * for the group of all members in the group.
+ *
+ * If not numeric, and more than one member, will throw an exception.
+ * Converts numeric values to doubles so they can be compared later.
+ *
+ * If cannot get at least one valid value, returns std::nullopt.
+ *
+ * @param[in] group - The group to get the max value of
+ *
+ * @param[in] manager - The Manager object
+ *
+ * @return optional<PropertyVariantType> - The value, or std::nullopt
+ */
+ std::optional<PropertyVariantType> getMaxGroupValue(const Group& group,
+ const Manager& manager);
+
+ /**
+ * @brief Returns a pointer to the group object specified
+ *
+ * Throws ActionParseError if no group found
+ *
+ * @param[in] name - The group name
+ *
+ * @return const Group* - Pointer to the group
+ */
+ const Group* getGroup(const std::string& name);
+
+ /* Key group pointer */
+ const Group* _keyGroup;
+
+ using FloorEntry = std::tuple<PropertyVariantType, uint64_t>;
+
+ struct FloorGroup
+ {
+ const Group* group;
+ std::vector<FloorEntry> floorEntries;
+ };
+
+ struct FanFloors
+ {
+ PropertyVariantType keyValue;
+ std::vector<FloorGroup> floorGroups;
+ };
+
+ /* The fan floors action data, loaded from JSON */
+ std::vector<FanFloors> _fanFloors;
+};
+
+} // namespace phosphor::fan::control::json