1848799f9SMatt Spinler /**
2848799f9SMatt Spinler  * Copyright © 2021 IBM Corporation
3848799f9SMatt Spinler  *
4848799f9SMatt Spinler  * Licensed under the Apache License, Version 2.0 (the "License");
5848799f9SMatt Spinler  * you may not use this file except in compliance with the License.
6848799f9SMatt Spinler  * You may obtain a copy of the License at
7848799f9SMatt Spinler  *
8848799f9SMatt Spinler  *     http://www.apache.org/licenses/LICENSE-2.0
9848799f9SMatt Spinler  *
10848799f9SMatt Spinler  * Unless required by applicable law or agreed to in writing, software
11848799f9SMatt Spinler  * distributed under the License is distributed on an "AS IS" BASIS,
12848799f9SMatt Spinler  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13848799f9SMatt Spinler  * See the License for the specific language governing permissions and
14848799f9SMatt Spinler  * limitations under the License.
15848799f9SMatt Spinler  */
16848799f9SMatt Spinler #pragma once
17848799f9SMatt Spinler 
18848799f9SMatt Spinler #include "../zone.hpp"
19848799f9SMatt Spinler #include "action.hpp"
20848799f9SMatt Spinler #include "group.hpp"
21848799f9SMatt Spinler 
22848799f9SMatt Spinler #include <nlohmann/json.hpp>
23848799f9SMatt Spinler 
24848799f9SMatt Spinler namespace phosphor::fan::control::json
25848799f9SMatt Spinler {
26848799f9SMatt Spinler 
27848799f9SMatt Spinler using json = nlohmann::json;
28848799f9SMatt Spinler 
29848799f9SMatt Spinler /**
30848799f9SMatt Spinler  * @class MappedFloor - Action to set a fan floor based on ranges of
31848799f9SMatt Spinler  *                      multiple sensor values.
32848799f9SMatt Spinler  * For example, consider the following config:
33848799f9SMatt Spinler  *
34848799f9SMatt Spinler  *    {
35848799f9SMatt Spinler  *    "name": "mapped_floor",
36848799f9SMatt Spinler  *    "key_group": "ambient_temp",
37848799f9SMatt Spinler  *    "fan_floors": [
38848799f9SMatt Spinler  *        {
39848799f9SMatt Spinler  *        "key": 27,
40848799f9SMatt Spinler  *        "floors": [
41848799f9SMatt Spinler  *          {
42848799f9SMatt Spinler  *            "group": "altitude",
43848799f9SMatt Spinler  *            "floors": [
44848799f9SMatt Spinler  *               {
45848799f9SMatt Spinler  *                 "value": 5000,
46848799f9SMatt Spinler  *                 "floor": 4500
47848799f9SMatt Spinler  *               }
48848799f9SMatt Spinler  *            ]
49848799f9SMatt Spinler  *          },
50848799f9SMatt Spinler  *          {
51848799f9SMatt Spinler  *            "group": "power_mode",
52848799f9SMatt Spinler  *            "floors": [
53848799f9SMatt Spinler  *               {
54848799f9SMatt Spinler  *                 "value": "MaximumPerformance",
55848799f9SMatt Spinler  *                 "floor": 5000
56848799f9SMatt Spinler  *               }
57848799f9SMatt Spinler  *            ]
58848799f9SMatt Spinler  *          }
59848799f9SMatt Spinler  *        ]
60848799f9SMatt Spinler  *        }
61848799f9SMatt Spinler  *      ]
62848799f9SMatt Spinler  *    }
63848799f9SMatt Spinler  *
64848799f9SMatt Spinler  * When it runs, it will:
65848799f9SMatt Spinler  *
66848799f9SMatt Spinler  * 1. Evaluate the key_group
67848799f9SMatt Spinler  *   - Find the max D-Bus property value (if numeric) of the member properties
68848799f9SMatt Spinler  *     in this group.
69848799f9SMatt Spinler  *   - Check it against each 'key' value in the fan_floor entries until
70848799f9SMatt Spinler  *     the key_group property value < key value, so:
71848799f9SMatt Spinler  *        max ambient temp < 27.
72848799f9SMatt Spinler  *   - If the above check passes, the rest of that entry will be evaluated
73848799f9SMatt Spinler  *     and then the action will be done.
74848799f9SMatt Spinler  *
75848799f9SMatt Spinler  * 2. Evaluate the group values in each floors array entry for this key value.
76848799f9SMatt Spinler  *   - Find the max D-Bus property value (if numeric) of the member properties
77848799f9SMatt Spinler  *     of this group - in this case 'altitude'.
78848799f9SMatt Spinler  *   - Depending on the data type of that group, compare to the 'value' entry:
79848799f9SMatt Spinler  *     - If numeric, check if the group's value is <= the 'value' one
80848799f9SMatt Spinler  *     - Otherwise, check if it is ==
81848799f9SMatt Spinler  *   - If that passes, save the value from the 'floor' entry and continue to
82848799f9SMatt Spinler  *     the next entry in the floors array, which, if present, would specify
83848799f9SMatt Spinler  *     another group to check.  In this case, 'power_mode'.  Repeat the above
84848799f9SMatt Spinler  *     step.
85848799f9SMatt Spinler  *   - After all the group compares are done, choose the largest floor value
86848799f9SMatt Spinler  *     to set the fan floor to.  If any group check results doesn't end in
87848799f9SMatt Spinler  *     a match being found, then the default floor will be set.
88848799f9SMatt Spinler  *
89848799f9SMatt Spinler  * Cases where the default floor will be set:
90848799f9SMatt Spinler  *  - A table entry can't be found based on a key group's value.
91848799f9SMatt Spinler  *  - A table entry can't be found based on a group's value.
92848799f9SMatt Spinler  *  - A value can't be obtained for the 'key_group' D-Bus property group.
93848799f9SMatt Spinler  *  - A value can't be obtained for any of the 'group' property groups.
94848799f9SMatt Spinler  *  - A value is NaN, as no <, <=, or == checks would succeed.
95848799f9SMatt Spinler  *
96848799f9SMatt Spinler  * Other notes:
97848799f9SMatt Spinler  *  - If a group has multiple members, they must be numeric or else
98848799f9SMatt Spinler  *    the code will throw an exception.
99*c981bb5bSMatt Spinler  *
100*c981bb5bSMatt Spinler  *  - The group inside the floors array can also be a Manager parameter, so that
101*c981bb5bSMatt Spinler  *    this action can operate on a parameter value set by another action.
102*c981bb5bSMatt Spinler  *
103*c981bb5bSMatt Spinler  *    So instead of
104*c981bb5bSMatt Spinler  *            "group": "altitude",
105*c981bb5bSMatt Spinler  *    it can be:
106*c981bb5bSMatt Spinler  *            "parameter": "some_parameter"
107848799f9SMatt Spinler  */
108848799f9SMatt Spinler 
109848799f9SMatt Spinler class MappedFloor : public ActionBase, public ActionRegister<MappedFloor>
110848799f9SMatt Spinler {
111848799f9SMatt Spinler   public:
112848799f9SMatt Spinler     /* Name of this action */
113848799f9SMatt Spinler     static constexpr auto name = "mapped_floor";
114848799f9SMatt Spinler 
115848799f9SMatt Spinler     MappedFloor() = delete;
116848799f9SMatt Spinler     MappedFloor(const MappedFloor&) = delete;
117848799f9SMatt Spinler     MappedFloor(MappedFloor&&) = delete;
118848799f9SMatt Spinler     MappedFloor& operator=(const MappedFloor&) = delete;
119848799f9SMatt Spinler     MappedFloor& operator=(MappedFloor&&) = delete;
120848799f9SMatt Spinler     ~MappedFloor() = default;
121848799f9SMatt Spinler 
122848799f9SMatt Spinler     /**
123848799f9SMatt Spinler      * @brief Parse the JSON to set the members
124848799f9SMatt Spinler      *
125848799f9SMatt Spinler      * @param[in] jsonObj - JSON configuration of this action
126848799f9SMatt Spinler      * @param[in] groups - Groups of dbus objects the action uses
127848799f9SMatt Spinler      */
128848799f9SMatt Spinler     MappedFloor(const json& jsonObj, const std::vector<Group>& groups);
129848799f9SMatt Spinler 
130848799f9SMatt Spinler     /**
131848799f9SMatt Spinler      * @brief Run the action.  See description above.
132848799f9SMatt Spinler      *
133848799f9SMatt Spinler      * @param[in] zone - Zone to run the action on
134848799f9SMatt Spinler      */
135848799f9SMatt Spinler     void run(Zone& zone) override;
136848799f9SMatt Spinler 
137848799f9SMatt Spinler   private:
138848799f9SMatt Spinler     /**
139848799f9SMatt Spinler      * @brief Parse and set the key group
140848799f9SMatt Spinler      *
141848799f9SMatt Spinler      * @param[in] jsonObj - JSON object for the action
142848799f9SMatt Spinler      */
143848799f9SMatt Spinler     void setKeyGroup(const json& jsonObj);
144848799f9SMatt Spinler 
145848799f9SMatt Spinler     /**
146848799f9SMatt Spinler      * @brief Parses and sets the floor group data members
147848799f9SMatt Spinler      *
148848799f9SMatt Spinler      * @param[in] jsonObj - JSON object for the action
149848799f9SMatt Spinler      */
150848799f9SMatt Spinler     void setFloorTable(const json& jsonObj);
151848799f9SMatt Spinler 
152848799f9SMatt Spinler     /**
153848799f9SMatt Spinler      * @brief Determines the maximum value of the property specified
154848799f9SMatt Spinler      *        for the group of all members in the group.
155848799f9SMatt Spinler      *
156848799f9SMatt Spinler      * If not numeric, and more than one member, will throw an exception.
157848799f9SMatt Spinler      * Converts numeric values to doubles so they can be compared later.
158848799f9SMatt Spinler      *
159848799f9SMatt Spinler      * If cannot get at least one valid value, returns std::nullopt.
160848799f9SMatt Spinler      *
161848799f9SMatt Spinler      * @param[in] group - The group to get the max value of
162848799f9SMatt Spinler      *
163848799f9SMatt Spinler      * @param[in] manager - The Manager object
164848799f9SMatt Spinler      *
165848799f9SMatt Spinler      * @return optional<PropertyVariantType> - The value, or std::nullopt
166848799f9SMatt Spinler      */
167848799f9SMatt Spinler     std::optional<PropertyVariantType> getMaxGroupValue(const Group& group,
168848799f9SMatt Spinler                                                         const Manager& manager);
169848799f9SMatt Spinler 
170848799f9SMatt Spinler     /**
171848799f9SMatt Spinler      * @brief Returns a pointer to the group object specified
172848799f9SMatt Spinler      *
173848799f9SMatt Spinler      * Throws ActionParseError if no group found
174848799f9SMatt Spinler      *
175848799f9SMatt Spinler      * @param[in] name - The group name
176848799f9SMatt Spinler      *
177848799f9SMatt Spinler      * @return const Group* - Pointer to the group
178848799f9SMatt Spinler      */
179848799f9SMatt Spinler     const Group* getGroup(const std::string& name);
180848799f9SMatt Spinler 
181848799f9SMatt Spinler     /* Key group pointer */
182848799f9SMatt Spinler     const Group* _keyGroup;
183848799f9SMatt Spinler 
184848799f9SMatt Spinler     using FloorEntry = std::tuple<PropertyVariantType, uint64_t>;
185848799f9SMatt Spinler 
186848799f9SMatt Spinler     struct FloorGroup
187848799f9SMatt Spinler     {
188*c981bb5bSMatt Spinler         std::variant<const Group*, std::string> groupOrParameter;
189848799f9SMatt Spinler         std::vector<FloorEntry> floorEntries;
190848799f9SMatt Spinler     };
191848799f9SMatt Spinler 
192848799f9SMatt Spinler     struct FanFloors
193848799f9SMatt Spinler     {
194848799f9SMatt Spinler         PropertyVariantType keyValue;
195848799f9SMatt Spinler         std::vector<FloorGroup> floorGroups;
196848799f9SMatt Spinler     };
197848799f9SMatt Spinler 
198848799f9SMatt Spinler     /* The fan floors action data, loaded from JSON */
199848799f9SMatt Spinler     std::vector<FanFloors> _fanFloors;
200848799f9SMatt Spinler };
201848799f9SMatt Spinler 
202848799f9SMatt Spinler } // namespace phosphor::fan::control::json
203