1 #pragma once
2 
3 #include <algorithm>
4 #include <numeric>
5 
6 namespace phosphor
7 {
8 namespace fan
9 {
10 namespace control
11 {
12 namespace action
13 {
14 
15 /**
16  * @brief An action to set the speed on a zone
17  * @details The zone is held at the given speed when a defined number of
18  * properties in the group are set to the given state
19  *
20  * @param[in] count - Number of properties
21  * @param[in] state - Value the property(s) needed to be set at
22  * @param[in] speed - Speed to set the zone to
23  *
24  * @return Lambda function
25  *     A lambda function to set the zone speed when the number of properties
26  *     within the group are at a certain value
27  */
28 template <typename T>
29 auto count_state_before_speed(size_t count, T&& state, uint64_t speed)
30 {
31     return [count,
32             speed,
33             state = std::forward<T>(state)](auto& zone, auto& group)
34     {
35         size_t numAtState = std::count_if(
36             group.begin(),
37             group.end(),
38             [&zone, &state](auto const& entry)
39             {
40                 try
41                 {
42                     return zone.template getPropertyValue<T>(
43                             entry.first,
44                             std::get<intfPos>(entry.second),
45                             std::get<propPos>(entry.second)) == state;
46                 }
47                 catch (const std::out_of_range& oore)
48                 {
49                     // Default to property not equal when not found
50                     return false;
51                 }
52             });
53         if (numAtState >= count)
54         {
55             zone.setSpeed(speed);
56         }
57         // Update group's fan control active allowed based on action results
58         zone.setActiveAllow(&group, !(numAtState >= count));
59     };
60 }
61 
62 /**
63  * @brief An action to set the floor speed on a zone
64  * @details Based on the average of the defined sensor group values, the floor
65  * speed is selected from the first map key entry that the average sensor value
66  * is less than.
67  *
68  * @param[in] val_to_speed - Ordered map of sensor value-to-speed
69  *
70  * @return Lambda function
71  *     A lambda function to set the zone's floor speed when the average of
72  *     property values within the group is below the lowest sensor value given
73  */
74 auto set_floor_from_average_sensor_value(
75         std::map<int64_t, uint64_t>&& val_to_speed)
76 {
77     return [val_to_speed = std::move(val_to_speed)](auto& zone, auto& group)
78     {
79         auto speed = zone.getDefFloor();
80         if (group.size() != 0)
81         {
82             auto count = 0;
83             auto sumValue = std::accumulate(
84                     group.begin(),
85                     group.end(),
86                     0,
87                     [&zone, &count](int64_t sum, auto const& entry)
88                     {
89                         try
90                         {
91                             return sum +
92                                 zone.template getPropertyValue<int64_t>(
93                                     entry.first,
94                                     std::get<intfPos>(entry.second),
95                                     std::get<propPos>(entry.second));
96                         }
97                         catch (const std::out_of_range& oore)
98                         {
99                             count++;
100                             return sum;
101                         }
102                     });
103             if ((group.size() - count) > 0)
104             {
105                 auto avgValue = sumValue / (group.size() - count);
106                 auto it = std::find_if(
107                     val_to_speed.begin(),
108                     val_to_speed.end(),
109                     [&avgValue](auto const& entry)
110                     {
111                         return avgValue < entry.first;
112                     }
113                 );
114                 if (it != std::end(val_to_speed))
115                 {
116                     speed = (*it).second;
117                 }
118             }
119         }
120         zone.setFloor(speed);
121     };
122 }
123 
124 /**
125  * @brief An action to set the ceiling speed on a zone
126  * @details Based on the average of the defined sensor group values, the ceiling
127  * speed is selected from the map key transition point that the average sensor
128  * value falls within depending on the key values direction from what was
129  * previously read.
130  *
131  * @param[in] val_to_speed - Ordered map of sensor value-to-speed transitions
132  *
133  * @return Lambda function
134  *     A lambda function to set the zone's ceiling speed when the average of
135  *     property values within the group is above(increasing) or
136  *     below(decreasing) the key transition point
137  */
138 auto set_ceiling_from_average_sensor_value(
139         std::map<int64_t, uint64_t>&& val_to_speed)
140 {
141     return [val_to_speed = std::move(val_to_speed)](auto& zone, auto& group)
142     {
143         auto speed = zone.getCeiling();
144         if (group.size() != 0)
145         {
146             auto count = 0;
147             auto sumValue = std::accumulate(
148                     group.begin(),
149                     group.end(),
150                     0,
151                     [&zone, &count](int64_t sum, auto const& entry)
152                     {
153                         try
154                         {
155                             return sum +
156                                 zone.template getPropertyValue<int64_t>(
157                                     entry.first,
158                                     std::get<intfPos>(entry.second),
159                                     std::get<propPos>(entry.second));
160                         }
161                         catch (const std::out_of_range& oore)
162                         {
163                             count++;
164                             return sum;
165                         }
166                     });
167             if ((group.size() - count) > 0)
168             {
169                 auto avgValue = sumValue / (group.size() - count);
170                 auto prevValue = zone.swapCeilingKeyValue(avgValue);
171                 if (avgValue != prevValue)
172                 {// Only check if previous and new values differ
173                     if (avgValue < prevValue)
174                     {// Value is decreasing from previous
175                         for (auto it = val_to_speed.rbegin();
176                              it != val_to_speed.rend();
177                              ++it)
178                         {
179                             if (it == val_to_speed.rbegin() &&
180                                 avgValue >= it->first)
181                             {
182                                 // Value is at/above last map key, set
183                                 // ceiling speed to the last map key's value
184                                 speed = it->second;
185                                 break;
186                             }
187                             else if (std::next(it, 1) == val_to_speed.rend() &&
188                                      avgValue <= it->first)
189                             {
190                                 // Value is at/below first map key, set
191                                 // ceiling speed to the first map key's value
192                                 speed = it->second;
193                                 break;
194                             }
195                             if (avgValue < it->first &&
196                                 it->first <= prevValue)
197                             {
198                                 // Value decreased & transitioned across
199                                 // a map key, update ceiling speed to this
200                                 // map key's value when new value is below
201                                 // map's key and the key is at/below the
202                                 // previous value
203                                 speed = it->second;
204                             }
205                         }
206                     }
207                     else
208                     {// Value is increasing from previous
209                         for (auto it = val_to_speed.begin();
210                              it != val_to_speed.end();
211                              ++it)
212                         {
213                             if (it == val_to_speed.begin() &&
214                                 avgValue <= it->first)
215                             {
216                                 // Value is at/below first map key, set
217                                 // ceiling speed to the first map key's value
218                                 speed = it->second;
219                                 break;
220                             }
221                             else if (std::next(it, 1) == val_to_speed.end() &&
222                                      avgValue >= it->first)
223                             {
224                                 // Value is at/above last map key, set
225                                 // ceiling speed to the last map key's value
226                                 speed = it->second;
227                                 break;
228                             }
229                             if (avgValue > it->first &&
230                                 it->first >= prevValue)
231                             {
232                                 // Value increased & transitioned across
233                                 // a map key, update ceiling speed to this
234                                 // map key's value when new value is above
235                                 // map's key and the key is at/above the
236                                 // previous value
237                                 speed = it->second;
238                             }
239                         }
240                     }
241                 }
242             }
243         }
244         zone.setCeiling(speed);
245     };
246 }
247 
248 /**
249  * @brief An action to set the speed increase delta and request speed change
250  * @details Provides the ability to determine what the net increase delta the
251  * zone's fan speeds should be updated by from their current target speed and
252  * request that new target speed.
253  *
254  * @param[in] state - State to compare the group's property value to
255  * @param[in] factor - Factor to apply to the calculated net delta
256  * @param[in] speedDelta - Speed delta of the group
257  *
258  * @return Lambda function
259  *     A lambda function that determines the net increase delta and requests
260  * a new target speed with that increase for the zone.
261  */
262 template <typename T>
263 auto set_net_increase_speed(T&& state, T&& factor, uint64_t speedDelta)
264 {
265     return [speedDelta,
266             factor = std::forward<T>(factor),
267             state = std::forward<T>(state)](auto& zone, auto& group)
268     {
269         auto netDelta = zone.getIncSpeedDelta();
270         std::for_each(
271             group.begin(),
272             group.end(),
273             [&zone, &state, &factor, &speedDelta, &netDelta](
274                 auto const& entry)
275             {
276                 try
277                 {
278                     T value = zone.template getPropertyValue<T>(
279                             entry.first,
280                             std::get<intfPos>(entry.second),
281                             std::get<propPos>(entry.second));
282                     // TODO openbmc/phosphor-fan-presence#7 - Support possible
283                     // state types for comparison
284                     if (value >= state)
285                     {
286                         // Increase by at least a single delta(factor)
287                         // to attempt bringing under 'state'
288                         auto delta = std::max(
289                             (value - state),
290                             factor);
291                         // Increase is the factor applied to the
292                         // difference times the given speed delta
293                         netDelta = std::max(
294                             netDelta,
295                             (delta/factor) * speedDelta);
296                     }
297                 }
298                 catch (const std::out_of_range& oore)
299                 {
300                     // Property value not found, netDelta unchanged
301                 }
302             }
303         );
304         // Request speed change for target speed update
305         zone.requestSpeedIncrease(netDelta);
306     };
307 }
308 
309 /**
310  * @brief An action to set the speed decrease delta and request speed change
311  * @details Provides the ability to determine what the net decrease delta each
312  * zone's fan speeds should be updated by from their current target speed, and
313  * request that speed change occur on the next decrease interval.
314  *
315  * @param[in] state - State to compare the group's property value to
316  * @param[in] factor - Factor to apply to the calculated net delta
317  * @param[in] speedDelta - Speed delta of the group
318  *
319  * @return Lambda function
320  *     A lambda function that determines the net decrease delta and requests
321  * a new target speed with that decrease for the zone.
322  */
323 template <typename T>
324 auto set_net_decrease_speed(T&& state, T&& factor, uint64_t speedDelta)
325 {
326     return [speedDelta,
327             factor = std::forward<T>(factor),
328             state = std::forward<T>(state)](auto& zone, auto& group)
329     {
330         auto netDelta = zone.getDecSpeedDelta();
331         std::for_each(
332             group.begin(),
333             group.end(),
334             [&zone, &state, &factor, &speedDelta, &netDelta](auto const& entry)
335             {
336                 try
337                 {
338                     T value = zone.template getPropertyValue<T>(
339                             entry.first,
340                             std::get<intfPos>(entry.second),
341                             std::get<propPos>(entry.second));
342                     // TODO openbmc/phosphor-fan-presence#7 - Support possible
343                     // state types for comparison
344                     if (value < state)
345                     {
346                         if (netDelta == 0)
347                         {
348                             netDelta = ((state - value)/factor) * speedDelta;
349                         }
350                         else
351                         {
352                             // Decrease is the factor applied to the
353                             // difference times the given speed delta
354                             netDelta = std::min(
355                                 netDelta,
356                                 ((state - value)/factor) * speedDelta);
357                         }
358                     }
359                 }
360                 catch (const std::out_of_range& oore)
361                 {
362                     // Property value not found, netDelta unchanged
363                 }
364             }
365         );
366         // Request speed decrease to occur on decrease interval
367         zone.requestSpeedDecrease(netDelta);
368     };
369 }
370 
371 } // namespace action
372 } // namespace control
373 } // namespace fan
374 } // namespace phosphor
375