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,
40*a17d5cc0SMatt Spinler  *        "floor_offset_parameter": "floor_27_offset",
41848799f9SMatt Spinler  *        "floors": [
42848799f9SMatt Spinler  *          {
43848799f9SMatt Spinler  *            "group": "altitude",
44848799f9SMatt Spinler  *            "floors": [
45848799f9SMatt Spinler  *               {
46848799f9SMatt Spinler  *                 "value": 5000,
47848799f9SMatt Spinler  *                 "floor": 4500
48848799f9SMatt Spinler  *               }
49848799f9SMatt Spinler  *            ]
50848799f9SMatt Spinler  *          },
51848799f9SMatt Spinler  *          {
52848799f9SMatt Spinler  *            "group": "power_mode",
53848799f9SMatt Spinler  *            "floors": [
54848799f9SMatt Spinler  *               {
55848799f9SMatt Spinler  *                 "value": "MaximumPerformance",
56848799f9SMatt Spinler  *                 "floor": 5000
57848799f9SMatt Spinler  *               }
58848799f9SMatt Spinler  *            ]
59848799f9SMatt Spinler  *          }
60848799f9SMatt Spinler  *        ]
61848799f9SMatt Spinler  *        }
62848799f9SMatt Spinler  *      ]
63848799f9SMatt Spinler  *    }
64848799f9SMatt Spinler  *
65848799f9SMatt Spinler  * When it runs, it will:
66848799f9SMatt Spinler  *
67848799f9SMatt Spinler  * 1. Evaluate the key_group
68848799f9SMatt Spinler  *   - Find the max D-Bus property value (if numeric) of the member properties
69848799f9SMatt Spinler  *     in this group.
70848799f9SMatt Spinler  *   - Check it against each 'key' value in the fan_floor entries until
71848799f9SMatt Spinler  *     the key_group property value < key value, so:
72848799f9SMatt Spinler  *        max ambient temp < 27.
73848799f9SMatt Spinler  *   - If the above check passes, the rest of that entry will be evaluated
74848799f9SMatt Spinler  *     and then the action will be done.
75848799f9SMatt Spinler  *
76848799f9SMatt Spinler  * 2. Evaluate the group values in each floors array entry for this key value.
77848799f9SMatt Spinler  *   - Find the max D-Bus property value (if numeric) of the member properties
78848799f9SMatt Spinler  *     of this group - in this case 'altitude'.
79848799f9SMatt Spinler  *   - Depending on the data type of that group, compare to the 'value' entry:
80848799f9SMatt Spinler  *     - If numeric, check if the group's value is <= the 'value' one
81848799f9SMatt Spinler  *     - Otherwise, check if it is ==
82848799f9SMatt Spinler  *   - If that passes, save the value from the 'floor' entry and continue to
83848799f9SMatt Spinler  *     the next entry in the floors array, which, if present, would specify
84848799f9SMatt Spinler  *     another group to check.  In this case, 'power_mode'.  Repeat the above
85848799f9SMatt Spinler  *     step.
86848799f9SMatt Spinler  *   - After all the group compares are done, choose the largest floor value
87*a17d5cc0SMatt Spinler  *     to set the fan floor to, but first apply the floor offset provided
88*a17d5cc0SMatt Spinler  *     by the parameter in the 'floor_offset_parameter' field, if it present.
89*a17d5cc0SMatt Spinler  *   - If any group check results doesn't end in a match being found, then
90*a17d5cc0SMatt Spinler  *     the default floor will be set.
91848799f9SMatt Spinler  *
92848799f9SMatt Spinler  * Cases where the default floor will be set:
93848799f9SMatt Spinler  *  - A table entry can't be found based on a key group's value.
94848799f9SMatt Spinler  *  - A table entry can't be found based on a group's value.
95848799f9SMatt Spinler  *  - A value can't be obtained for the 'key_group' D-Bus property group.
96848799f9SMatt Spinler  *  - A value can't be obtained for any of the 'group' property groups.
97848799f9SMatt Spinler  *  - A value is NaN, as no <, <=, or == checks would succeed.
98848799f9SMatt Spinler  *
99848799f9SMatt Spinler  * Other notes:
100848799f9SMatt Spinler  *  - If a group has multiple members, they must be numeric or else
101848799f9SMatt Spinler  *    the code will throw an exception.
102c981bb5bSMatt Spinler  *
103c981bb5bSMatt Spinler  *  - The group inside the floors array can also be a Manager parameter, so that
104c981bb5bSMatt Spinler  *    this action can operate on a parameter value set by another action.
105c981bb5bSMatt Spinler  *
106c981bb5bSMatt Spinler  *    So instead of
107c981bb5bSMatt Spinler  *            "group": "altitude",
108c981bb5bSMatt Spinler  *    it can be:
109c981bb5bSMatt Spinler  *            "parameter": "some_parameter"
110848799f9SMatt Spinler  */
111848799f9SMatt Spinler 
112848799f9SMatt Spinler class MappedFloor : public ActionBase, public ActionRegister<MappedFloor>
113848799f9SMatt Spinler {
114848799f9SMatt Spinler   public:
115848799f9SMatt Spinler     /* Name of this action */
116848799f9SMatt Spinler     static constexpr auto name = "mapped_floor";
117848799f9SMatt Spinler 
118848799f9SMatt Spinler     MappedFloor() = delete;
119848799f9SMatt Spinler     MappedFloor(const MappedFloor&) = delete;
120848799f9SMatt Spinler     MappedFloor(MappedFloor&&) = delete;
121848799f9SMatt Spinler     MappedFloor& operator=(const MappedFloor&) = delete;
122848799f9SMatt Spinler     MappedFloor& operator=(MappedFloor&&) = delete;
123848799f9SMatt Spinler     ~MappedFloor() = default;
124848799f9SMatt Spinler 
125848799f9SMatt Spinler     /**
126848799f9SMatt Spinler      * @brief Parse the JSON to set the members
127848799f9SMatt Spinler      *
128848799f9SMatt Spinler      * @param[in] jsonObj - JSON configuration of this action
129848799f9SMatt Spinler      * @param[in] groups - Groups of dbus objects the action uses
130848799f9SMatt Spinler      */
131848799f9SMatt Spinler     MappedFloor(const json& jsonObj, const std::vector<Group>& groups);
132848799f9SMatt Spinler 
133848799f9SMatt Spinler     /**
134848799f9SMatt Spinler      * @brief Run the action.  See description above.
135848799f9SMatt Spinler      *
136848799f9SMatt Spinler      * @param[in] zone - Zone to run the action on
137848799f9SMatt Spinler      */
138848799f9SMatt Spinler     void run(Zone& zone) override;
139848799f9SMatt Spinler 
140848799f9SMatt Spinler   private:
141848799f9SMatt Spinler     /**
142848799f9SMatt Spinler      * @brief Parse and set the key group
143848799f9SMatt Spinler      *
144848799f9SMatt Spinler      * @param[in] jsonObj - JSON object for the action
145848799f9SMatt Spinler      */
146848799f9SMatt Spinler     void setKeyGroup(const json& jsonObj);
147848799f9SMatt Spinler 
148848799f9SMatt Spinler     /**
149848799f9SMatt Spinler      * @brief Parses and sets the floor group data members
150848799f9SMatt Spinler      *
151848799f9SMatt Spinler      * @param[in] jsonObj - JSON object for the action
152848799f9SMatt Spinler      */
153848799f9SMatt Spinler     void setFloorTable(const json& jsonObj);
154848799f9SMatt Spinler 
155848799f9SMatt Spinler     /**
156*a17d5cc0SMatt Spinler      * @brief Applies the offset in offsetParameter to the
157*a17d5cc0SMatt Spinler      *        value passed in.
158*a17d5cc0SMatt Spinler      *
159*a17d5cc0SMatt Spinler      * If offsetParameter is empty then no offset will be
160*a17d5cc0SMatt Spinler      * applied.
161*a17d5cc0SMatt Spinler      *
162*a17d5cc0SMatt Spinler      * Note: The offset may be negative.
163*a17d5cc0SMatt Spinler      *
164*a17d5cc0SMatt Spinler      * @param[in] floor - The floor to apply offset to
165*a17d5cc0SMatt Spinler      * @param[in] offsetParameter - The floor offset parameter
166*a17d5cc0SMatt Spinler      *
167*a17d5cc0SMatt Spinler      * @return uint64_t - The new floor value
168*a17d5cc0SMatt Spinler      */
169*a17d5cc0SMatt Spinler     uint64_t applyFloorOffset(uint64_t floor,
170*a17d5cc0SMatt Spinler                               const std::string& offsetParameter) const;
171*a17d5cc0SMatt Spinler 
172*a17d5cc0SMatt Spinler     /**
173848799f9SMatt Spinler      * @brief Determines the maximum value of the property specified
174848799f9SMatt Spinler      *        for the group of all members in the group.
175848799f9SMatt Spinler      *
176848799f9SMatt Spinler      * If not numeric, and more than one member, will throw an exception.
177848799f9SMatt Spinler      * Converts numeric values to doubles so they can be compared later.
178848799f9SMatt Spinler      *
179848799f9SMatt Spinler      * If cannot get at least one valid value, returns std::nullopt.
180848799f9SMatt Spinler      *
181848799f9SMatt Spinler      * @param[in] group - The group to get the max value of
182848799f9SMatt Spinler      *
183848799f9SMatt Spinler      * @param[in] manager - The Manager object
184848799f9SMatt Spinler      *
185848799f9SMatt Spinler      * @return optional<PropertyVariantType> - The value, or std::nullopt
186848799f9SMatt Spinler      */
187848799f9SMatt Spinler     std::optional<PropertyVariantType> getMaxGroupValue(const Group& group,
188848799f9SMatt Spinler                                                         const Manager& manager);
189848799f9SMatt Spinler 
190848799f9SMatt Spinler     /**
191848799f9SMatt Spinler      * @brief Returns a pointer to the group object specified
192848799f9SMatt Spinler      *
193848799f9SMatt Spinler      * Throws ActionParseError if no group found
194848799f9SMatt Spinler      *
195848799f9SMatt Spinler      * @param[in] name - The group name
196848799f9SMatt Spinler      *
197848799f9SMatt Spinler      * @return const Group* - Pointer to the group
198848799f9SMatt Spinler      */
199848799f9SMatt Spinler     const Group* getGroup(const std::string& name);
200848799f9SMatt Spinler 
201848799f9SMatt Spinler     /* Key group pointer */
202848799f9SMatt Spinler     const Group* _keyGroup;
203848799f9SMatt Spinler 
204848799f9SMatt Spinler     using FloorEntry = std::tuple<PropertyVariantType, uint64_t>;
205848799f9SMatt Spinler 
206848799f9SMatt Spinler     struct FloorGroup
207848799f9SMatt Spinler     {
208c981bb5bSMatt Spinler         std::variant<const Group*, std::string> groupOrParameter;
209848799f9SMatt Spinler         std::vector<FloorEntry> floorEntries;
210848799f9SMatt Spinler     };
211848799f9SMatt Spinler 
212848799f9SMatt Spinler     struct FanFloors
213848799f9SMatt Spinler     {
214848799f9SMatt Spinler         PropertyVariantType keyValue;
215*a17d5cc0SMatt Spinler         std::string offsetParameter;
216848799f9SMatt Spinler         std::vector<FloorGroup> floorGroups;
217848799f9SMatt Spinler     };
218848799f9SMatt Spinler 
219848799f9SMatt Spinler     /* The fan floors action data, loaded from JSON */
220848799f9SMatt Spinler     std::vector<FanFloors> _fanFloors;
221848799f9SMatt Spinler };
222848799f9SMatt Spinler 
223848799f9SMatt Spinler } // namespace phosphor::fan::control::json
224