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 #endif
149         }
150     }
151 
152     // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
153     percent /= 100;
154 
155     // PidSensorMap for writing.
156     for (const auto& it : _inputs)
157     {
158         auto sensor = _owner->getSensor(it);
159         auto redundantWrite = _owner->getRedundantWrite();
160         int64_t rawWritten;
161         sensor->write(percent, redundantWrite, &rawWritten);
162     }
163 
164     return;
165 }
166 
167 } // namespace pid_control
168