1848799f9SMatt Spinler /**
2*b2e9a4fcSMike 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  *
104848799f9SMatt Spinler  * Other notes:
105848799f9SMatt Spinler  *  - If a group has multiple members, they must be numeric or else
106848799f9SMatt Spinler  *    the code will throw an exception.
107c981bb5bSMatt Spinler  *
108c981bb5bSMatt Spinler  *  - The group inside the floors array can also be a Manager parameter, so that
109c981bb5bSMatt Spinler  *    this action can operate on a parameter value set by another action.
110c981bb5bSMatt Spinler  *
111c981bb5bSMatt Spinler  *    So instead of
112c981bb5bSMatt Spinler  *            "group": "altitude",
113c981bb5bSMatt Spinler  *    it can be:
114c981bb5bSMatt Spinler  *            "parameter": "some_parameter"
115848799f9SMatt Spinler  */
116848799f9SMatt Spinler 
117848799f9SMatt Spinler class MappedFloor : public ActionBase, public ActionRegister<MappedFloor>
118848799f9SMatt Spinler {
119848799f9SMatt Spinler   public:
120848799f9SMatt Spinler     /* Name of this action */
121848799f9SMatt Spinler     static constexpr auto name = "mapped_floor";
122848799f9SMatt Spinler 
123848799f9SMatt Spinler     MappedFloor() = delete;
124848799f9SMatt Spinler     MappedFloor(const MappedFloor&) = delete;
125848799f9SMatt Spinler     MappedFloor(MappedFloor&&) = delete;
126848799f9SMatt Spinler     MappedFloor& operator=(const MappedFloor&) = delete;
127848799f9SMatt Spinler     MappedFloor& operator=(MappedFloor&&) = delete;
128848799f9SMatt Spinler     ~MappedFloor() = default;
129848799f9SMatt Spinler 
130848799f9SMatt Spinler     /**
131848799f9SMatt Spinler      * @brief Parse the JSON to set the members
132848799f9SMatt Spinler      *
133848799f9SMatt Spinler      * @param[in] jsonObj - JSON configuration of this action
134848799f9SMatt Spinler      * @param[in] groups - Groups of dbus objects the action uses
135848799f9SMatt Spinler      */
136848799f9SMatt Spinler     MappedFloor(const json& jsonObj, const std::vector<Group>& groups);
137848799f9SMatt Spinler 
138848799f9SMatt Spinler     /**
139848799f9SMatt Spinler      * @brief Run the action.  See description above.
140848799f9SMatt Spinler      *
141848799f9SMatt Spinler      * @param[in] zone - Zone to run the action on
142848799f9SMatt Spinler      */
143848799f9SMatt Spinler     void run(Zone& zone) override;
144848799f9SMatt Spinler 
145848799f9SMatt Spinler   private:
146848799f9SMatt Spinler     /**
147848799f9SMatt Spinler      * @brief Parse and set the key group
148848799f9SMatt Spinler      *
149848799f9SMatt Spinler      * @param[in] jsonObj - JSON object for the action
150848799f9SMatt Spinler      */
151848799f9SMatt Spinler     void setKeyGroup(const json& jsonObj);
152848799f9SMatt Spinler 
153848799f9SMatt Spinler     /**
15476ef2013SMatt Spinler      * @brief Parse and set the default floor value
15576ef2013SMatt Spinler      *
15676ef2013SMatt Spinler      * @param[in] jsonObj - JSON object for the action
15776ef2013SMatt Spinler      */
15876ef2013SMatt Spinler     void setDefaultFloor(const json& jsonObj);
15976ef2013SMatt Spinler 
16076ef2013SMatt Spinler     /**
161848799f9SMatt Spinler      * @brief Parses and sets the floor group data members
162848799f9SMatt Spinler      *
163848799f9SMatt Spinler      * @param[in] jsonObj - JSON object for the action
164848799f9SMatt Spinler      */
165848799f9SMatt Spinler     void setFloorTable(const json& jsonObj);
166848799f9SMatt Spinler 
167848799f9SMatt Spinler     /**
168a17d5cc0SMatt Spinler      * @brief Applies the offset in offsetParameter to the
169a17d5cc0SMatt Spinler      *        value passed in.
170a17d5cc0SMatt Spinler      *
171a17d5cc0SMatt Spinler      * If offsetParameter is empty then no offset will be
172a17d5cc0SMatt Spinler      * applied.
173a17d5cc0SMatt Spinler      *
174a17d5cc0SMatt Spinler      * Note: The offset may be negative.
175a17d5cc0SMatt Spinler      *
176a17d5cc0SMatt Spinler      * @param[in] floor - The floor to apply offset to
177a17d5cc0SMatt Spinler      * @param[in] offsetParameter - The floor offset parameter
178a17d5cc0SMatt Spinler      *
179a17d5cc0SMatt Spinler      * @return uint64_t - The new floor value
180a17d5cc0SMatt Spinler      */
181a17d5cc0SMatt Spinler     uint64_t applyFloorOffset(uint64_t floor,
182a17d5cc0SMatt Spinler                               const std::string& offsetParameter) const;
183a17d5cc0SMatt Spinler 
184a17d5cc0SMatt Spinler     /**
185848799f9SMatt Spinler      * @brief Determines the maximum value of the property specified
186848799f9SMatt Spinler      *        for the group of all members in the group.
187848799f9SMatt Spinler      *
188848799f9SMatt Spinler      * If not numeric, and more than one member, will throw an exception.
189848799f9SMatt Spinler      * Converts numeric values to doubles so they can be compared later.
190848799f9SMatt Spinler      *
191848799f9SMatt Spinler      * If cannot get at least one valid value, returns std::nullopt.
192848799f9SMatt Spinler      *
193848799f9SMatt Spinler      * @param[in] group - The group to get the max value of
194848799f9SMatt Spinler      *
195848799f9SMatt Spinler      * @return optional<PropertyVariantType> - The value, or std::nullopt
196848799f9SMatt Spinler      */
197*b2e9a4fcSMike Capps     std::optional<PropertyVariantType> getMaxGroupValue(const Group& group);
198848799f9SMatt Spinler 
199848799f9SMatt Spinler     /**
200848799f9SMatt Spinler      * @brief Returns a pointer to the group object specified
201848799f9SMatt Spinler      *
202848799f9SMatt Spinler      * Throws ActionParseError if no group found
203848799f9SMatt Spinler      *
204848799f9SMatt Spinler      * @param[in] name - The group name
205848799f9SMatt Spinler      *
206848799f9SMatt Spinler      * @return const Group* - Pointer to the group
207848799f9SMatt Spinler      */
208848799f9SMatt Spinler     const Group* getGroup(const std::string& name);
209848799f9SMatt Spinler 
210848799f9SMatt Spinler     /* Key group pointer */
211848799f9SMatt Spinler     const Group* _keyGroup;
212848799f9SMatt Spinler 
21376ef2013SMatt Spinler     /* Optional default floor value for the action */
21476ef2013SMatt Spinler     std::optional<uint64_t> _defaultFloor;
21576ef2013SMatt Spinler 
216848799f9SMatt Spinler     using FloorEntry = std::tuple<PropertyVariantType, uint64_t>;
217848799f9SMatt Spinler 
218848799f9SMatt Spinler     struct FloorGroup
219848799f9SMatt Spinler     {
220c981bb5bSMatt Spinler         std::variant<const Group*, std::string> groupOrParameter;
221848799f9SMatt Spinler         std::vector<FloorEntry> floorEntries;
222848799f9SMatt Spinler     };
223848799f9SMatt Spinler 
224848799f9SMatt Spinler     struct FanFloors
225848799f9SMatt Spinler     {
226848799f9SMatt Spinler         PropertyVariantType keyValue;
227a17d5cc0SMatt Spinler         std::string offsetParameter;
22876ef2013SMatt Spinler         std::optional<uint64_t> defaultFloor;
229848799f9SMatt Spinler         std::vector<FloorGroup> floorGroups;
230848799f9SMatt Spinler     };
231848799f9SMatt Spinler 
232848799f9SMatt Spinler     /* The fan floors action data, loaded from JSON */
233848799f9SMatt Spinler     std::vector<FanFloors> _fanFloors;
234848799f9SMatt Spinler };
235848799f9SMatt Spinler 
236848799f9SMatt Spinler } // namespace phosphor::fan::control::json
237