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         if (_owner->getFailSafeMode())
134         {
135             double failsafePercent = _owner->getFailSafePercent();
136 
137 #ifdef STRICT_FAILSAFE_PWM
138             // Unconditionally replace the computed PWM with the
139             // failsafe PWM if STRICT_FAILSAFE_PWM is defined.
140             percent = failsafePercent;
141 #else
142             // Ensure PWM is never lower than the failsafe PWM.
143             // The computed PWM is still allowed to rise higher than
144             // failsafe PWM if STRICT_FAILSAFE_PWM is NOT defined.
145             // This is the default behavior.
146             if (percent < failsafePercent)
147             {
148                 percent = failsafePercent;
149             }
150 
151             if (failsafePrint || debugEnabled)
152             {
153                 std::cerr << "Zone " << _owner->getZoneID()
154                           << " fans output failsafe pwm: " << percent << "\n";
155                 failsafePrint = false;
156             }
157 #endif
158         }
159         else
160         {
161             failsafePrint = true;
162             if (debugEnabled)
163             {
164                 std::cerr << "Zone " << _owner->getZoneID()
165                           << " fans output pwm: " << percent << "\n";
166             }
167         }
168     }
169 
170     // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
171     percent /= 100.0;
172 
173     // PidSensorMap for writing.
174     for (const auto& it : _inputs)
175     {
176         auto sensor = _owner->getSensor(it);
177         auto redundantWrite = _owner->getRedundantWrite();
178         int64_t rawWritten = -1;
179         sensor->write(percent, redundantWrite, &rawWritten);
180 
181         // The outputCache will be used later,
182         // to store a record of the PWM commanded,
183         // so that this information can be included during logging.
184         auto unscaledWritten = static_cast<double>(rawWritten);
185         _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
186     }
187 
188     return;
189 }
190 
191 } // namespace pid_control
192