xref: /openbmc/phosphor-fan-presence/control/actions.hpp (revision b99ce0ed9fbb06a765eeb801e61fdba6abcdf9dc)
1  #pragma once
2  
3  #include "types.hpp"
4  #include "utility.hpp"
5  #include "zone.hpp"
6  
7  #include <algorithm>
8  #include <numeric>
9  
10  namespace phosphor
11  {
12  namespace fan
13  {
14  namespace control
15  {
16  namespace action
17  {
18  
19  /**
20   * @brief An action that wraps a list of actions with a timer
21   * @details Sets up a list of actions to be invoked when the defined timer
22   * expires (or for each expiration of a repeating timer).
23   *
24   * @param[in] tConf - Timer configuration parameters
25   * @param[in] action - List of actions to be called when the timer expires
26   *
27   * @return Action lambda function
28   *     An Action function that creates a timer
29   */
30  Action call_actions_based_on_timer(TimerConf&& tConf,
31                                     std::vector<Action>&& actions);
32  
33  /**
34   * @brief An action that sets the floor to the default fan floor speed
35   * @details Sets the fan floor to the defined default fan floor speed when a
36   * service associated to the given group has terminated. Once all services
37   * are functional and providing the sensors again, the fan floor is allowed
38   * to be set normally.
39   *
40   * @param[in] zone - Zone containing fans
41   * @param[in] group - Group of sensors to determine services' states
42   */
43  void default_floor_on_missing_owner(Zone& zone, const Group& group);
44  
45  /**
46   * @brief An action to set a speed when a service owner is missing
47   * @details Sets the fans to the given speed when any service owner associated
48   * to the group is missing. Once all services are functional and providing
49   * the event data again, active fan speed changes are allowed.
50   *
51   * @param[in] speed - Speed to set the zone to
52   *
53   * @return Action lambda function
54   *     An Action function that sets the zone to the given speed if any service
55   *     owners are missing.
56   */
57  Action set_speed_on_missing_owner(uint64_t speed);
58  
59  /**
60   * @brief An action to set the request speed base
61   * @details A new target speed is determined using a speed delta being added
62   * or subtracted, for increases or decrease respectively, from a base speed.
63   * This base speed defaults to be the current target speed or is set to a
64   * different base speed(i.e. the fans' tach feedback speed) to request a new
65   * target from.
66   *
67   * @param[in] zone - Zone containing fans
68   * @param[in] group - Group of sensors to determine base from
69   */
70  void set_request_speed_base_with_max(Zone& zone, const Group& group);
71  
72  /**
73   * @brief An action to set the speed on a zone
74   * @details The zone is held at the given speed when a defined number of
75   * properties in the group are set to the given state
76   *
77   * @param[in] count - Number of properties
78   * @param[in] state - Value the property(s) needed to be set at
79   * @param[in] speed - Speed to set the zone to
80   *
81   * @return Lambda function
82   *     A lambda function to set the zone speed when the number of properties
83   *     within the group are at a certain value
84   */
85  template <typename T>
86  auto count_state_before_speed(size_t count, T&& state, uint64_t speed)
87  {
88      return [count, speed, state = std::forward<T>(state)](auto& zone,
89                                                            auto& group) {
90          size_t numAtState = 0;
91          for (auto& entry : group)
92          {
93              try
94              {
95                  if (zone.template getPropertyValue<T>(
96                          std::get<pathPos>(entry), std::get<intfPos>(entry),
97                          std::get<propPos>(entry)) == state)
98                  {
99                      numAtState++;
100                  }
101              }
102              catch (const std::out_of_range& oore)
103              {
104                  // Default to property not equal when not found
105              }
106              if (numAtState >= count)
107              {
108                  zone.setSpeed(speed);
109                  break;
110              }
111          }
112          // Update group's fan control active allowed based on action results
113          zone.setActiveAllow(&group, !(numAtState >= count));
114      };
115  }
116  
117  /**
118   * @brief An action to set the floor speed on a zone
119   * @details Based on the average of the defined sensor group values, the floor
120   * speed is selected from the first map key entry that the average sensor value
121   * is less than.
122   *
123   * @param[in] val_to_speed - Ordered map of sensor value-to-speed
124   *
125   * @return Action lambda function
126   *     An Action function to set the zone's floor speed when the average of
127   *     property values within the group is below the lowest sensor value given
128   */
129  template <typename T>
130  Action set_floor_from_average_sensor_value(std::map<T, uint64_t>&& val_to_speed)
131  {
132      return [val_to_speed = std::move(val_to_speed)](control::Zone& zone,
133                                                      const Group& group) {
134          auto speed = zone.getDefFloor();
135          if (group.size() != 0)
136          {
137              auto count = 0;
138              auto sumValue =
139                  std::accumulate(group.begin(), group.end(), 0,
140                                  [&zone, &count](T sum, auto const& entry) {
141                  try
142                  {
143                      return sum + zone.template getPropertyValue<T>(
144                                       std::get<pathPos>(entry),
145                                       std::get<intfPos>(entry),
146                                       std::get<propPos>(entry));
147                  }
148                  catch (const std::out_of_range& oore)
149                  {
150                      count++;
151                      return sum;
152                  }
153              });
154              if ((group.size() - count) > 0)
155              {
156                  auto groupSize = static_cast<int64_t>(group.size());
157                  auto avgValue = sumValue / (groupSize - count);
158                  auto it = std::find_if(val_to_speed.begin(), val_to_speed.end(),
159                                         [&avgValue](auto const& entry) {
160                      return avgValue < entry.first;
161                  });
162                  if (it != std::end(val_to_speed))
163                  {
164                      speed = (*it).second;
165                  }
166              }
167          }
168          zone.setFloor(speed);
169      };
170  }
171  
172  /**
173   * @brief An action to set the ceiling speed on a zone
174   * @details Based on the average of the defined sensor group values, the
175   * ceiling speed is selected from the map key transition point that the average
176   * sensor value falls within depending on the key values direction from what
177   * was previously read.
178   *
179   * @param[in] val_to_speed - Ordered map of sensor value-to-speed transitions
180   *
181   * @return Action lambda function
182   *     An Action function to set the zone's ceiling speed when the average of
183   *     property values within the group is above(increasing) or
184   *     below(decreasing) the key transition point
185   */
186  template <typename T>
187  Action
188      set_ceiling_from_average_sensor_value(std::map<T, uint64_t>&& val_to_speed)
189  {
190      return [val_to_speed = std::move(val_to_speed)](Zone& zone,
191                                                      const Group& group) {
192          auto speed = zone.getCeiling();
193          if (group.size() != 0)
194          {
195              auto count = 0;
196              auto sumValue =
197                  std::accumulate(group.begin(), group.end(), 0,
198                                  [&zone, &count](T sum, auto const& entry) {
199                  try
200                  {
201                      return sum + zone.template getPropertyValue<T>(
202                                       std::get<pathPos>(entry),
203                                       std::get<intfPos>(entry),
204                                       std::get<propPos>(entry));
205                  }
206                  catch (const std::out_of_range& oore)
207                  {
208                      count++;
209                      return sum;
210                  }
211              });
212              if ((group.size() - count) > 0)
213              {
214                  auto groupSize = static_cast<int64_t>(group.size());
215                  auto avgValue = sumValue / (groupSize - count);
216                  auto prevValue = zone.swapCeilingKeyValue(avgValue);
217                  if (avgValue != prevValue)
218                  {     // Only check if previous and new values differ
219                      if (avgValue < prevValue)
220                      { // Value is decreasing from previous
221                          for (auto it = val_to_speed.rbegin();
222                               it != val_to_speed.rend(); ++it)
223                          {
224                              if (it == val_to_speed.rbegin() &&
225                                  avgValue >= it->first)
226                              {
227                                  // Value is at/above last map key, set
228                                  // ceiling speed to the last map key's value
229                                  speed = it->second;
230                                  break;
231                              }
232                              else if (std::next(it, 1) == val_to_speed.rend() &&
233                                       avgValue <= it->first)
234                              {
235                                  // Value is at/below first map key, set
236                                  // ceiling speed to the first map key's value
237                                  speed = it->second;
238                                  break;
239                              }
240                              if (avgValue < it->first && it->first <= prevValue)
241                              {
242                                  // Value decreased & transitioned across
243                                  // a map key, update ceiling speed to this
244                                  // map key's value when new value is below
245                                  // map's key and the key is at/below the
246                                  // previous value
247                                  speed = it->second;
248                              }
249                          }
250                      }
251                      else
252                      { // Value is increasing from previous
253                          for (auto it = val_to_speed.begin();
254                               it != val_to_speed.end(); ++it)
255                          {
256                              if (it == val_to_speed.begin() &&
257                                  avgValue <= it->first)
258                              {
259                                  // Value is at/below first map key, set
260                                  // ceiling speed to the first map key's value
261                                  speed = it->second;
262                                  break;
263                              }
264                              else if (std::next(it, 1) == val_to_speed.end() &&
265                                       avgValue >= it->first)
266                              {
267                                  // Value is at/above last map key, set
268                                  // ceiling speed to the last map key's value
269                                  speed = it->second;
270                                  break;
271                              }
272                              if (avgValue > it->first && it->first >= prevValue)
273                              {
274                                  // Value increased & transitioned across
275                                  // a map key, update ceiling speed to this
276                                  // map key's value when new value is above
277                                  // map's key and the key is at/above the
278                                  // previous value
279                                  speed = it->second;
280                              }
281                          }
282                      }
283                  }
284              }
285          }
286          zone.setCeiling(speed);
287      };
288  }
289  
290  /**
291   * @brief An action to set the speed increase delta and request speed change
292   * @details Provides the ability to determine what the net increase delta the
293   * zone's fan speeds should be updated by from their current target speed and
294   * request that new target speed.
295   *
296   * @param[in] state - State to compare the group's property value to
297   * @param[in] factor - Factor to apply to the calculated net delta
298   * @param[in] speedDelta - Speed delta of the group
299   *
300   * @return Lambda function
301   *     A lambda function that determines the net increase delta and requests
302   * a new target speed with that increase for the zone.
303   */
304  template <typename T>
305  auto set_net_increase_speed(T&& state, T&& factor, uint64_t speedDelta)
306  {
307      return [speedDelta, factor = std::forward<T>(factor),
308              state = std::forward<T>(state)](auto& zone, auto& group) {
309          auto netDelta = zone.getIncSpeedDelta();
310          std::for_each(group.begin(), group.end(),
311                        [&zone, &state, &factor, &speedDelta,
312                         &netDelta](auto const& entry) {
313              try
314              {
315                  T value = zone.template getPropertyValue<T>(
316                      std::get<pathPos>(entry), std::get<intfPos>(entry),
317                      std::get<propPos>(entry));
318                  // TODO openbmc/phosphor-fan-presence#7 - Support possible
319                  // state types for comparison
320                  if (value >= state)
321                  {
322                      // Increase by at least a single delta(factor)
323                      // to attempt bringing under 'state'
324                      auto delta = std::max((value - state), factor);
325                      // Increase is the factor applied to the
326                      // difference times the given speed delta
327                      netDelta = std::max(
328                          netDelta,
329                          static_cast<uint64_t>((delta / factor) * speedDelta));
330                  }
331              }
332              catch (const std::out_of_range& oore)
333              {
334                  // Property value not found, netDelta unchanged
335              }
336          });
337          // Request speed change for target speed update
338          zone.requestSpeedIncrease(netDelta);
339      };
340  }
341  
342  /**
343   * @brief An action to set the speed decrease delta and request speed change
344   * @details Provides the ability to determine what the net decrease delta each
345   * zone's fan speeds should be updated by from their current target speed, and
346   * request that speed change occur on the next decrease interval.
347   *
348   * @param[in] state - State to compare the group's property value to
349   * @param[in] factor - Factor to apply to the calculated net delta
350   * @param[in] speedDelta - Speed delta of the group
351   *
352   * @return Lambda function
353   *     A lambda function that determines the net decrease delta and requests
354   * a new target speed with that decrease for the zone.
355   */
356  template <typename T>
357  auto set_net_decrease_speed(T&& state, T&& factor, uint64_t speedDelta)
358  {
359      return [speedDelta, factor = std::forward<T>(factor),
360              state = std::forward<T>(state)](auto& zone, auto& group) {
361          auto netDelta = zone.getDecSpeedDelta();
362          for (auto& entry : group)
363          {
364              try
365              {
366                  T value = zone.template getPropertyValue<T>(
367                      std::get<pathPos>(entry), std::get<intfPos>(entry),
368                      std::get<propPos>(entry));
369                  // TODO openbmc/phosphor-fan-presence#7 - Support possible
370                  // state types for comparison
371                  if (value < state)
372                  {
373                      if (netDelta == 0)
374                      {
375                          netDelta = ((state - value) / factor) * speedDelta;
376                      }
377                      else
378                      {
379                          // Decrease is the factor applied to the
380                          // difference times the given speed delta
381                          netDelta = std::min(
382                              netDelta,
383                              static_cast<uint64_t>(((state - value) / factor) *
384                                                    speedDelta));
385                      }
386                  }
387                  else
388                  {
389                      // No decrease allowed for this group
390                      netDelta = 0;
391                      break;
392                  }
393              }
394              catch (const std::out_of_range& oore)
395              {
396                  // Property value not found, netDelta unchanged
397              }
398          }
399          // Update group's decrease allowed state
400          zone.setDecreaseAllow(&group, !(netDelta == 0));
401          // Request speed decrease to occur on decrease interval
402          zone.requestSpeedDecrease(netDelta);
403      };
404  }
405  
406  /**
407   * @brief An action to use an alternate set of events
408   * @details Provides the ability to replace a default set of events with an
409   * alternate set of events based on all members of a group being at a specified
410   * state. When any member of the group no longer matches the provided state,
411   * the alternate set of events are replaced with the defaults.
412   *
413   * @param[in] state - State to compare the group's property value to
414   * @param[in] defEvents - The default set of events
415   * @param[in] altEvents - The alternate set of events
416   *
417   * @return Lambda function
418   *     A lambda function that checks all group members are at a specified state
419   * and replacing the default set of events with an alternate set of events.
420   */
421  template <typename T>
422  auto use_alternate_events_on_state(T&& state,
423                                     std::vector<SetSpeedEvent>&& defEvents,
424                                     std::vector<SetSpeedEvent>&& altEvents)
425  {
426      return [state = std::forward<T>(state), defEvents = std::move(defEvents),
427              altEvents = std::move(altEvents)](auto& zone, auto& group) {
428          // Compare all group entries to the state
429          auto useAlt = std::all_of(group.begin(), group.end(),
430                                    [&zone, &state](auto const& entry) {
431              try
432              {
433                  return zone.template getPropertyValue<T>(
434                             std::get<pathPos>(entry), std::get<intfPos>(entry),
435                             std::get<propPos>(entry)) == state;
436              }
437              catch (const std::out_of_range& oore)
438              {
439                  // Default to property not equal when not found
440                  return false;
441              }
442          });
443  
444          const std::vector<SetSpeedEvent>* rmEvents = &altEvents;
445          const std::vector<SetSpeedEvent>* initEvents = &defEvents;
446  
447          if (useAlt)
448          {
449              rmEvents = &defEvents;
450              initEvents = &altEvents;
451          }
452  
453          // Remove events
454          std::for_each(rmEvents->begin(), rmEvents->end(),
455                        [&zone](auto const& entry) { zone.removeEvent(entry); });
456          // Init events
457          std::for_each(initEvents->begin(), initEvents->end(),
458                        [&zone](auto const& entry) { zone.initEvent(entry); });
459      };
460  }
461  
462  /**
463   * @brief An action to set the floor speed on a zone
464   * @details Using sensor group values that are within a defined range, the
465   * floor speed is selected from the first map key entry that the median
466   * sensor value is less than where 3 or more sensor group values are valid.
467   * In the case where less than 3 sensor values are valid, use the highest
468   * sensor group value and default the floor speed when 0 sensor group values
469   * are valid.
470   *
471   * @param[in] lowerBound - Lowest allowed sensor value to be valid
472   * @param[in] upperBound - Highest allowed sensor value to be valid
473   * @param[in] valueToSpeed - Ordered map of sensor value-to-speed
474   *
475   * @return Action lambda function
476   *     An Action function to set the zone's floor speed from a resulting group
477   * of valid sensor values based on their highest value or median.
478   */
479  template <typename T>
480  Action set_floor_from_median_sensor_value(T&& lowerBound, T&& upperBound,
481                                            std::map<T, uint64_t>&& valueToSpeed)
482  {
483      return [lowerBound = std::forward<T>(lowerBound),
484              upperBound = std::forward<T>(upperBound),
485              valueToSpeed = std::move(valueToSpeed)](control::Zone& zone,
486                                                      const Group& group) {
487          auto speed = zone.getDefFloor();
488          if (group.size() != 0)
489          {
490              std::vector<T> validValues;
491              for (auto const& member : group)
492              {
493                  try
494                  {
495                      auto value = zone.template getPropertyValue<T>(
496                          std::get<pathPos>(member), std::get<intfPos>(member),
497                          std::get<propPos>(member));
498                      if (value == std::clamp(value, lowerBound, upperBound))
499                      {
500                          // Sensor value is valid
501                          validValues.emplace_back(value);
502                      }
503                  }
504                  catch (const std::out_of_range& oore)
505                  {
506                      continue;
507                  }
508              }
509  
510              if (!validValues.empty())
511              {
512                  auto median = validValues.front();
513                  // Get the determined median value
514                  if (validValues.size() == 2)
515                  {
516                      // For 2 values, use the highest instead of the average
517                      // for a thermally safe floor
518                      median = *std::max_element(validValues.begin(),
519                                                 validValues.end());
520                  }
521                  else if (validValues.size() > 2)
522                  {
523                      median = utility::getMedian(validValues);
524                  }
525  
526                  // Use determined median sensor value to find floor speed
527                  auto it = std::find_if(valueToSpeed.begin(), valueToSpeed.end(),
528                                         [&median](auto const& entry) {
529                      return median < entry.first;
530                  });
531                  if (it != std::end(valueToSpeed))
532                  {
533                      speed = (*it).second;
534                  }
535              }
536          }
537          zone.setFloor(speed);
538      };
539  }
540  
541  /**
542   * @brief An action to update the default floor speed
543   * @details Provides the ability to update the default fan floor speed when
544   * all of the group members property values match the value given
545   *
546   * @param[in] state - State to compare the group's property value to
547   * @param[in] speed - Speed to set the default fan floor to
548   *
549   * @return Lambda function
550   *     A lambda function that checks all group members are at a specified state
551   * and updates the default fan floor speed.
552   */
553  template <typename T>
554  auto update_default_floor(T&& state, uint64_t speed)
555  {
556      return [speed, state = std::forward<T>(state)](auto& zone, auto& group) {
557          auto updateDefFloor = std::all_of(group.begin(), group.end(),
558                                            [&zone, &state](auto const& entry) {
559              try
560              {
561                  return zone.template getPropertyValue<T>(
562                             std::get<pathPos>(entry), std::get<intfPos>(entry),
563                             std::get<propPos>(entry)) == state;
564              }
565              catch (const std::out_of_range& oore)
566              {
567                  // Default to property not equal when not found
568                  return false;
569              }
570          });
571  
572          if (!updateDefFloor)
573          {
574              // Do not update the default floor
575              return;
576          }
577  
578          // Set/update the default floor of the zone
579          zone.setDefFloor(speed);
580      };
581  }
582  
583  /**
584   * @brief An action to use a set of events
585   * @details Provides the ability to use a set of events when all members of
586   * a group are at a specified state. When any member of the group no longer
587   * matches the provided state the set of events are removed.
588   *
589   * @param[in] state - State to compare the group's property value to
590   * @param[in] events - The set of events
591   *
592   * @return Lambda function
593   *     A lambda function that checks all group members are at a specified state
594   * and initializes the set of events, otherwise removes them.
595   */
596  template <typename T>
597  auto use_events_on_state(T&& state, std::vector<SetSpeedEvent>&& events)
598  {
599      return [state = std::forward<T>(state),
600              events = std::move(events)](auto& zone, auto& group) {
601          // Compare all group entries to the state
602          auto useEvents = std::all_of(group.begin(), group.end(),
603                                       [&zone, &state](auto const& entry) {
604              try
605              {
606                  return zone.template getPropertyValue<T>(
607                             std::get<pathPos>(entry), std::get<intfPos>(entry),
608                             std::get<propPos>(entry)) == state;
609              }
610              catch (const std::out_of_range& oore)
611              {
612                  // Default to property not equal when not found
613                  return false;
614              }
615          });
616  
617          if (useEvents)
618          {
619              // Init events
620              std::for_each(
621                  events.begin(), events.end(),
622                  [&zone](auto const& entry) { zone.initEvent(entry); });
623          }
624          else
625          {
626              // Remove events
627              std::for_each(
628                  events.begin(), events.end(),
629                  [&zone](auto const& entry) { zone.removeEvent(entry); });
630          }
631      };
632  }
633  
634  } // namespace action
635  } // namespace control
636  } // namespace fan
637  } // namespace phosphor
638