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;
50     std::vector<int64_t> values;
51     std::vector<int64_t>::iterator result;
52 
53     try
54     {
55         for (const auto& name : _inputs)
56         {
57             value = _owner->getCachedValue(name);
58             /* If we have a fan we can't read, its value will be 0 for at least
59              * some boards, while others... the fan will drop off dbus (if
60              * that's how it's being read and in that case its value will never
61              * be updated anymore, which is relatively harmless, except, when
62              * something tries to read its value through IPMI, and can't, they
63              * sort of have to guess -- all the other fans are reporting, why
64              * not this one?  Maybe it's unable to be read, so it's "bad."
65              */
66             if (!(std::isfinite(value)))
67             {
68                 continue;
69             }
70             if (value <= 0)
71             {
72                 continue;
73             }
74 
75             values.push_back(value);
76         }
77     }
78     catch (const std::exception& e)
79     {
80         std::cerr << "exception on inputProc.\n";
81         throw;
82     }
83 
84     /* Reset the value from the above loop. */
85     value = 0;
86     if (values.size() > 0)
87     {
88         /* the fan PID algorithm was unstable with average, and seemed to work
89          * better with minimum.  I had considered making this choice a variable
90          * in the configuration, and it's a nice-to-have..
91          */
92         result = std::min_element(values.begin(), values.end());
93         value = *result;
94     }
95 
96     return value;
97 }
98 
99 double FanController::setptProc(void)
100 {
101     double maxRPM = _owner->getMaxSetPointRequest();
102 
103     // store for reference, and check if more or less.
104     double prev = getSetpoint();
105 
106     if (maxRPM > prev)
107     {
108         setFanDirection(FanSpeedDirection::UP);
109     }
110     else if (prev > maxRPM)
111     {
112         setFanDirection(FanSpeedDirection::DOWN);
113     }
114     else
115     {
116         setFanDirection(FanSpeedDirection::NEUTRAL);
117     }
118 
119     setSetpoint(maxRPM);
120 
121     return (maxRPM);
122 }
123 
124 void FanController::outputProc(double value)
125 {
126     double percent = value;
127 
128     /* If doing tuning, don't go into failsafe mode. */
129     if (!tuningEnabled)
130     {
131         if (_owner->getFailSafeMode())
132         {
133             double failsafePercent = _owner->getFailSafePercent();
134 
135 #ifdef STRICT_FAILSAFE_PWM
136             // Unconditionally replace the computed PWM with the
137             // failsafe PWM if STRICT_FAILSAFE_PWM is defined.
138             percent = failsafePercent;
139 #else
140             // Ensure PWM is never lower than the failsafe PWM.
141             // The computed PWM is still allowed to rise higher than
142             // failsafe PWM if STRICT_FAILSAFE_PWM is NOT defined.
143             // This is the default behavior.
144             if (percent < failsafePercent)
145             {
146                 percent = failsafePercent;
147             }
148 
149             if (failsafePrint || debugEnabled)
150             {
151                 std::cerr << "Zone " << _owner->getZoneID()
152                           << " fans output failsafe pwm: " << percent << "\n";
153                 failsafePrint = false;
154             }
155 #endif
156         }
157         else
158         {
159             failsafePrint = true;
160             if (debugEnabled)
161             {
162                 std::cerr << "Zone " << _owner->getZoneID()
163                           << " fans output pwm: " << percent << "\n";
164             }
165         }
166     }
167 
168     // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
169     percent /= 100;
170 
171     // PidSensorMap for writing.
172     for (const auto& it : _inputs)
173     {
174         auto sensor = _owner->getSensor(it);
175         auto redundantWrite = _owner->getRedundantWrite();
176         int64_t rawWritten;
177         sensor->write(percent, redundantWrite, &rawWritten);
178 
179         // The outputCache will be used later,
180         // to store a record of the PWM commanded,
181         // so that this information can be included during logging.
182         auto unscaledWritten = static_cast<double>(rawWritten);
183         _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
184     }
185 
186     return;
187 }
188 
189 } // namespace pid_control
190