1848799f9SMatt Spinler /**
2b2e9a4fcSMike Capps  * Copyright © 2022 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",
3776ef2013SMatt Spinler  *    "default_floor": 2000,
38848799f9SMatt Spinler  *    "fan_floors": [
39848799f9SMatt Spinler  *        {
40848799f9SMatt Spinler  *        "key": 27,
41a17d5cc0SMatt Spinler  *        "floor_offset_parameter": "floor_27_offset",
4276ef2013SMatt Spinler  *        "default_floor": 3000,
43848799f9SMatt Spinler  *        "floors": [
44848799f9SMatt Spinler  *          {
45848799f9SMatt Spinler  *            "group": "altitude",
46848799f9SMatt Spinler  *            "floors": [
47848799f9SMatt Spinler  *               {
48848799f9SMatt Spinler  *                 "value": 5000,
49848799f9SMatt Spinler  *                 "floor": 4500
50848799f9SMatt Spinler  *               }
51848799f9SMatt Spinler  *            ]
52848799f9SMatt Spinler  *          },
53848799f9SMatt Spinler  *          {
54848799f9SMatt Spinler  *            "group": "power_mode",
55848799f9SMatt Spinler  *            "floors": [
56848799f9SMatt Spinler  *               {
57848799f9SMatt Spinler  *                 "value": "MaximumPerformance",
58848799f9SMatt Spinler  *                 "floor": 5000
59848799f9SMatt Spinler  *               }
60848799f9SMatt Spinler  *            ]
61848799f9SMatt Spinler  *          }
62848799f9SMatt Spinler  *        ]
63848799f9SMatt Spinler  *        }
64848799f9SMatt Spinler  *      ]
65848799f9SMatt Spinler  *    }
66848799f9SMatt Spinler  *
67848799f9SMatt Spinler  * When it runs, it will:
68848799f9SMatt Spinler  *
69848799f9SMatt Spinler  * 1. Evaluate the key_group
70848799f9SMatt Spinler  *   - Find the max D-Bus property value (if numeric) of the member properties
71848799f9SMatt Spinler  *     in this group.
72848799f9SMatt Spinler  *   - Check it against each 'key' value in the fan_floor entries until
73848799f9SMatt Spinler  *     the key_group property value < key value, so:
74848799f9SMatt Spinler  *        max ambient temp < 27.
75848799f9SMatt Spinler  *   - If the above check passes, the rest of that entry will be evaluated
76848799f9SMatt Spinler  *     and then the action will be done.
77848799f9SMatt Spinler  *
78848799f9SMatt Spinler  * 2. Evaluate the group values in each floors array entry for this key value.
79848799f9SMatt Spinler  *   - Find the max D-Bus property value (if numeric) of the member properties
80848799f9SMatt Spinler  *     of this group - in this case 'altitude'.
81848799f9SMatt Spinler  *   - Depending on the data type of that group, compare to the 'value' entry:
82848799f9SMatt Spinler  *     - If numeric, check if the group's value is <= the 'value' one
83848799f9SMatt Spinler  *     - Otherwise, check if it is ==
84848799f9SMatt Spinler  *   - If that passes, save the value from the 'floor' entry and continue to
85848799f9SMatt Spinler  *     the next entry in the floors array, which, if present, would specify
86848799f9SMatt Spinler  *     another group to check.  In this case, 'power_mode'.  Repeat the above
87848799f9SMatt Spinler  *     step.
88848799f9SMatt Spinler  *   - After all the group compares are done, choose the largest floor value
89a17d5cc0SMatt Spinler  *     to set the fan floor to, but first apply the floor offset provided
90a17d5cc0SMatt Spinler  *     by the parameter in the 'floor_offset_parameter' field, if it present.
91a17d5cc0SMatt Spinler  *   - If any group check results doesn't end in a match being found, then
92a17d5cc0SMatt Spinler  *     the default floor will be set.
93848799f9SMatt Spinler  *
9476ef2013SMatt Spinler  * There are up to 3 default value values that may be used when a regular
9576ef2013SMatt Spinler  * floor value can't be calculated:
9676ef2013SMatt Spinler  *   1. Optional default floor at the key group level
9776ef2013SMatt Spinler  *     - Chosen when a floor wasn't found in any floor groups
9876ef2013SMatt Spinler  *   2. Optional default floor at the action level
9976ef2013SMatt Spinler  *     - Chosen when there isn't a key table found for the key value,
10076ef2013SMatt Spinler  *       or 1. above occurred but that default floor wasn't supplied.
10176ef2013SMatt Spinler  *   3. The default floor in the zone config
10276ef2013SMatt Spinler  *     - Chosen when when 2. would be used, but it wasn't supplied
103848799f9SMatt Spinler  *
104*1a555607SMatt Spinler  * This action can also have a condition specified where a group property
105*1a555607SMatt Spinler  * must either match or not match a given value to determine if the
106*1a555607SMatt Spinler  * action should run or not.  This requires the following in the JSON:
107*1a555607SMatt Spinler  *    "condition_group": The group name
108*1a555607SMatt Spinler  *       - As of now, group must just have a single member.
109*1a555607SMatt Spinler  *    "condition_op": Either "equal" or "not_equal"
110*1a555607SMatt Spinler  *    "condition_value": The value to check against
111*1a555607SMatt Spinler  *
112*1a555607SMatt Spinler  * This allows completely separate mapped_floor actions to run based on
113*1a555607SMatt Spinler  * the value of a D-bus property - i.e. it allows multiple floor tables.
114*1a555607SMatt Spinler  *
115848799f9SMatt Spinler  * Other notes:
116848799f9SMatt Spinler  *  - If a group has multiple members, they must be numeric or else
117848799f9SMatt Spinler  *    the code will throw an exception.
118c981bb5bSMatt Spinler  *
119c981bb5bSMatt Spinler  *  - The group inside the floors array can also be a Manager parameter, so that
120c981bb5bSMatt Spinler  *    this action can operate on a parameter value set by another action.
121c981bb5bSMatt Spinler  *
122c981bb5bSMatt Spinler  *    So instead of
123c981bb5bSMatt Spinler  *            "group": "altitude",
124c981bb5bSMatt Spinler  *    it can be:
125c981bb5bSMatt Spinler  *            "parameter": "some_parameter"
126848799f9SMatt Spinler  */
127848799f9SMatt Spinler class MappedFloor : public ActionBase, public ActionRegister<MappedFloor>
128848799f9SMatt Spinler {
129848799f9SMatt Spinler   public:
130848799f9SMatt Spinler     /* Name of this action */
131848799f9SMatt Spinler     static constexpr auto name = "mapped_floor";
132848799f9SMatt Spinler 
133848799f9SMatt Spinler     MappedFloor() = delete;
134848799f9SMatt Spinler     MappedFloor(const MappedFloor&) = delete;
135848799f9SMatt Spinler     MappedFloor(MappedFloor&&) = delete;
136848799f9SMatt Spinler     MappedFloor& operator=(const MappedFloor&) = delete;
137848799f9SMatt Spinler     MappedFloor& operator=(MappedFloor&&) = delete;
138848799f9SMatt Spinler     ~MappedFloor() = default;
139848799f9SMatt Spinler 
140848799f9SMatt Spinler     /**
141848799f9SMatt Spinler      * @brief Parse the JSON to set the members
142848799f9SMatt Spinler      *
143848799f9SMatt Spinler      * @param[in] jsonObj - JSON configuration of this action
144848799f9SMatt Spinler      * @param[in] groups - Groups of dbus objects the action uses
145848799f9SMatt Spinler      */
146848799f9SMatt Spinler     MappedFloor(const json& jsonObj, const std::vector<Group>& groups);
147848799f9SMatt Spinler 
148848799f9SMatt Spinler     /**
149848799f9SMatt Spinler      * @brief Run the action.  See description above.
150848799f9SMatt Spinler      *
151848799f9SMatt Spinler      * @param[in] zone - Zone to run the action on
152848799f9SMatt Spinler      */
153848799f9SMatt Spinler     void run(Zone& zone) override;
154848799f9SMatt Spinler 
155848799f9SMatt Spinler   private:
156848799f9SMatt Spinler     /**
157848799f9SMatt Spinler      * @brief Parse and set the key group
158848799f9SMatt Spinler      *
159848799f9SMatt Spinler      * @param[in] jsonObj - JSON object for the action
160848799f9SMatt Spinler      */
161848799f9SMatt Spinler     void setKeyGroup(const json& jsonObj);
162848799f9SMatt Spinler 
163848799f9SMatt Spinler     /**
16476ef2013SMatt Spinler      * @brief Parse and set the default floor value
16576ef2013SMatt Spinler      *
16676ef2013SMatt Spinler      * @param[in] jsonObj - JSON object for the action
16776ef2013SMatt Spinler      */
16876ef2013SMatt Spinler     void setDefaultFloor(const json& jsonObj);
16976ef2013SMatt Spinler 
17076ef2013SMatt Spinler     /**
171848799f9SMatt Spinler      * @brief Parses and sets the floor group data members
172848799f9SMatt Spinler      *
173848799f9SMatt Spinler      * @param[in] jsonObj - JSON object for the action
174848799f9SMatt Spinler      */
175848799f9SMatt Spinler     void setFloorTable(const json& jsonObj);
176848799f9SMatt Spinler 
177848799f9SMatt Spinler     /**
178*1a555607SMatt Spinler      * @brief Parse and set the conditions
179*1a555607SMatt Spinler      *
180*1a555607SMatt Spinler      * @param jsonObj - JSON object for the action
181*1a555607SMatt Spinler      */
182*1a555607SMatt Spinler     void setCondition(const json& jsonObj);
183*1a555607SMatt Spinler 
184*1a555607SMatt Spinler     /**
185a17d5cc0SMatt Spinler      * @brief Applies the offset in offsetParameter to the
186a17d5cc0SMatt Spinler      *        value passed in.
187a17d5cc0SMatt Spinler      *
188a17d5cc0SMatt Spinler      * If offsetParameter is empty then no offset will be
189a17d5cc0SMatt Spinler      * applied.
190a17d5cc0SMatt Spinler      *
191a17d5cc0SMatt Spinler      * Note: The offset may be negative.
192a17d5cc0SMatt Spinler      *
193a17d5cc0SMatt Spinler      * @param[in] floor - The floor to apply offset to
194a17d5cc0SMatt Spinler      * @param[in] offsetParameter - The floor offset parameter
195a17d5cc0SMatt Spinler      *
196a17d5cc0SMatt Spinler      * @return uint64_t - The new floor value
197a17d5cc0SMatt Spinler      */
198a17d5cc0SMatt Spinler     uint64_t applyFloorOffset(uint64_t floor,
199a17d5cc0SMatt Spinler                               const std::string& offsetParameter) const;
200a17d5cc0SMatt Spinler 
201a17d5cc0SMatt Spinler     /**
202848799f9SMatt Spinler      * @brief Determines the maximum value of the property specified
203848799f9SMatt Spinler      *        for the group of all members in the group.
204848799f9SMatt Spinler      *
205848799f9SMatt Spinler      * If not numeric, and more than one member, will throw an exception.
206848799f9SMatt Spinler      * Converts numeric values to doubles so they can be compared later.
207848799f9SMatt Spinler      *
208848799f9SMatt Spinler      * If cannot get at least one valid value, returns std::nullopt.
209848799f9SMatt Spinler      *
210848799f9SMatt Spinler      * @param[in] group - The group to get the max value of
211848799f9SMatt Spinler      *
212848799f9SMatt Spinler      * @return optional<PropertyVariantType> - The value, or std::nullopt
213848799f9SMatt Spinler      */
214b2e9a4fcSMike Capps     std::optional<PropertyVariantType> getMaxGroupValue(const Group& group);
215848799f9SMatt Spinler 
216848799f9SMatt Spinler     /**
217848799f9SMatt Spinler      * @brief Returns a pointer to the group object specified
218848799f9SMatt Spinler      *
219848799f9SMatt Spinler      * Throws ActionParseError if no group found
220848799f9SMatt Spinler      *
221848799f9SMatt Spinler      * @param[in] name - The group name
222848799f9SMatt Spinler      *
223848799f9SMatt Spinler      * @return const Group* - Pointer to the group
224848799f9SMatt Spinler      */
225848799f9SMatt Spinler     const Group* getGroup(const std::string& name);
226848799f9SMatt Spinler 
227*1a555607SMatt Spinler     /**
228*1a555607SMatt Spinler      * @brief Checks if the condition is met, if there is one.
229*1a555607SMatt Spinler      *
230*1a555607SMatt Spinler      * @return bool - False if there is a condition and it
231*1a555607SMatt Spinler      *                isn't met, true otherwise.
232*1a555607SMatt Spinler      */
233*1a555607SMatt Spinler     bool meetsCondition();
234*1a555607SMatt Spinler 
235848799f9SMatt Spinler     /* Key group pointer */
236848799f9SMatt Spinler     const Group* _keyGroup;
237848799f9SMatt Spinler 
238*1a555607SMatt Spinler     /* condition group pointer */
239*1a555607SMatt Spinler     const Group* _conditionGroup = nullptr;
240*1a555607SMatt Spinler 
241*1a555607SMatt Spinler     /* Condition value */
242*1a555607SMatt Spinler     PropertyVariantType _conditionValue;
243*1a555607SMatt Spinler 
244*1a555607SMatt Spinler     /* Condition operation */
245*1a555607SMatt Spinler     std::string _conditionOp;
246*1a555607SMatt Spinler 
24776ef2013SMatt Spinler     /* Optional default floor value for the action */
24876ef2013SMatt Spinler     std::optional<uint64_t> _defaultFloor;
24976ef2013SMatt Spinler 
250848799f9SMatt Spinler     using FloorEntry = std::tuple<PropertyVariantType, uint64_t>;
251848799f9SMatt Spinler 
252848799f9SMatt Spinler     struct FloorGroup
253848799f9SMatt Spinler     {
254c981bb5bSMatt Spinler         std::variant<const Group*, std::string> groupOrParameter;
255848799f9SMatt Spinler         std::vector<FloorEntry> floorEntries;
256848799f9SMatt Spinler     };
257848799f9SMatt Spinler 
258848799f9SMatt Spinler     struct FanFloors
259848799f9SMatt Spinler     {
260848799f9SMatt Spinler         PropertyVariantType keyValue;
261a17d5cc0SMatt Spinler         std::string offsetParameter;
26276ef2013SMatt Spinler         std::optional<uint64_t> defaultFloor;
263848799f9SMatt Spinler         std::vector<FloorGroup> floorGroups;
264848799f9SMatt Spinler     };
265848799f9SMatt Spinler 
266848799f9SMatt Spinler     /* The fan floors action data, loaded from JSON */
267848799f9SMatt Spinler     std::vector<FanFloors> _fanFloors;
268848799f9SMatt Spinler };
269848799f9SMatt Spinler 
270848799f9SMatt Spinler } // namespace phosphor::fan::control::json
271