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