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 
17 #include "fancontroller.hpp"
18 
19 #include "tuning.hpp"
20 #include "util.hpp"
21 #include "zone.hpp"
22 
23 #include <algorithm>
24 #include <cmath>
25 #include <iostream>
26 
27 namespace pid_control
28 {
29 
30 std::unique_ptr<PIDController>
31     FanController::createFanPid(ZoneInterface* owner, const std::string& id,
32                                 const std::vector<std::string>& inputs,
33                                 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 
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 
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 
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 
213 } // namespace pid_control
214