1 /**
2  * Copyright 2017 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "config.h"
17 
18 #include "fancontroller.hpp"
19 
20 #include "tuning.hpp"
21 #include "util.hpp"
22 #include "zone.hpp"
23 
24 #include <algorithm>
25 #include <cmath>
26 #include <iostream>
27 
28 namespace pid_control
29 {
30 
31 std::unique_ptr<PIDController>
32     FanController::createFanPid(ZoneInterface* owner, const std::string& id,
33                                 const std::vector<std::string>& inputs,
34                                 const ec::pidinfo& initial)
35 {
36     if (inputs.size() == 0)
37     {
38         return nullptr;
39     }
40     auto fan = std::make_unique<FanController>(id, inputs, owner);
41     ec::pid_info_t* info = fan->getPIDInfo();
42 
43     initializePIDStruct(info, initial);
44 
45     return fan;
46 }
47 
48 double FanController::inputProc(void)
49 {
50     double value = 0.0;
51     std::vector<double> values;
52     std::vector<double>::iterator result;
53 
54     try
55     {
56         for (const auto& name : _inputs)
57         {
58             // Read the unscaled value, to correctly recover the RPM
59             value = _owner->getCachedValues(name).unscaled;
60 
61             /* If we have a fan we can't read, its value will be 0 for at least
62              * some boards, while others... the fan will drop off dbus (if
63              * that's how it's being read and in that case its value will never
64              * be updated anymore, which is relatively harmless, except, when
65              * something tries to read its value through IPMI, and can't, they
66              * sort of have to guess -- all the other fans are reporting, why
67              * not this one?  Maybe it's unable to be read, so it's "bad."
68              */
69             if (!(std::isfinite(value)))
70             {
71                 continue;
72             }
73             if (value <= 0.0)
74             {
75                 continue;
76             }
77 
78             values.push_back(value);
79         }
80     }
81     catch (const std::exception& e)
82     {
83         std::cerr << "exception on inputProc.\n";
84         throw;
85     }
86 
87     /* Reset the value from the above loop. */
88     value = 0.0;
89     if (values.size() > 0)
90     {
91         /* the fan PID algorithm was unstable with average, and seemed to work
92          * better with minimum.  I had considered making this choice a variable
93          * in the configuration, and it's a nice-to-have..
94          */
95         result = std::min_element(values.begin(), values.end());
96         value = *result;
97     }
98 
99     return value;
100 }
101 
102 double FanController::setptProc(void)
103 {
104     double maxRPM = _owner->getMaxSetPointRequest();
105 
106     // store for reference, and check if more or less.
107     double prev = getSetpoint();
108 
109     if (maxRPM > prev)
110     {
111         setFanDirection(FanSpeedDirection::UP);
112     }
113     else if (prev > maxRPM)
114     {
115         setFanDirection(FanSpeedDirection::DOWN);
116     }
117     else
118     {
119         setFanDirection(FanSpeedDirection::NEUTRAL);
120     }
121 
122     setSetpoint(maxRPM);
123 
124     return (maxRPM);
125 }
126 
127 void FanController::outputProc(double value)
128 {
129     double percent = value;
130 
131     /* If doing tuning, don't go into failsafe mode. */
132     if (!tuningEnabled)
133     {
134         bool failsafeCurrState = _owner->getFailSafeMode();
135 
136         // Note when failsafe state transitions happen
137         if (failsafePrevState != failsafeCurrState)
138         {
139             failsafePrevState = failsafeCurrState;
140             failsafeTransition = true;
141         }
142 
143         if (failsafeCurrState)
144         {
145             double failsafePercent = _owner->getFailSafePercent();
146 
147 #ifdef STRICT_FAILSAFE_PWM
148             // Unconditionally replace the computed PWM with the
149             // failsafe PWM if STRICT_FAILSAFE_PWM is defined.
150             percent = failsafePercent;
151 #else
152             // Ensure PWM is never lower than the failsafe PWM.
153             // The computed PWM is still allowed to rise higher than
154             // failsafe PWM if STRICT_FAILSAFE_PWM is NOT defined.
155             // This is the default behavior.
156             if (percent < failsafePercent)
157             {
158                 percent = failsafePercent;
159             }
160 #endif
161         }
162 
163         // Always print if debug enabled
164         if (debugEnabled)
165         {
166             std::cerr << "Zone " << _owner->getZoneID() << " fans, "
167                       << (failsafeCurrState ? "failsafe" : "normal")
168                       << " mode, output pwm: " << percent << "\n";
169         }
170         else
171         {
172             // Only print once per transition when not debugging
173             if (failsafeTransition)
174             {
175                 failsafeTransition = false;
176                 std::cerr << "Zone " << _owner->getZoneID() << " fans, "
177                           << (failsafeCurrState ? "entering failsafe"
178                                                 : "returning to normal")
179                           << " mode, output pwm: " << percent << "\n";
180             }
181         }
182     }
183     else
184     {
185         if (debugEnabled)
186         {
187             std::cerr << "Zone " << _owner->getZoneID()
188                       << " fans, tuning mode, bypassing failsafe, output pwm: "
189                       << percent << "\n";
190         }
191     }
192 
193     // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
194     percent /= 100.0;
195 
196     // PidSensorMap for writing.
197     for (const auto& it : _inputs)
198     {
199         auto sensor = _owner->getSensor(it);
200         auto redundantWrite = _owner->getRedundantWrite();
201         int64_t rawWritten = -1;
202         sensor->write(percent, redundantWrite, &rawWritten);
203 
204         // The outputCache will be used later,
205         // to store a record of the PWM commanded,
206         // so that this information can be included during logging.
207         auto unscaledWritten = static_cast<double>(rawWritten);
208         _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
209     }
210 
211     return;
212 }
213 
214 FanController::~FanController()
215 {
216 #ifdef OFFLINE_FAILSAFE_PWM
217     double percent = _owner->getFailSafePercent();
218     if (debugEnabled)
219     {
220         std::cerr << "Zone " << _owner->getZoneID()
221                   << " offline fans output pwm: " << percent << "\n";
222     }
223 
224     // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
225     percent /= 100.0;
226 
227     // PidSensorMap for writing.
228     for (const auto& it : _inputs)
229     {
230         auto sensor = _owner->getSensor(it);
231         auto redundantWrite = _owner->getRedundantWrite();
232         int64_t rawWritten;
233         sensor->write(percent, redundantWrite, &rawWritten);
234 
235         // The outputCache will be used later,
236         // to store a record of the PWM commanded,
237         // so that this information can be included during logging.
238         auto unscaledWritten = static_cast<double>(rawWritten);
239         _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
240     }
241 #endif
242 }
243 
244 } // namespace pid_control
245