1d8012181SPatrick Venture /**
2d8012181SPatrick Venture  * Copyright 2017 Google Inc.
3d8012181SPatrick Venture  *
4d8012181SPatrick Venture  * Licensed under the Apache License, Version 2.0 (the "License");
5d8012181SPatrick Venture  * you may not use this file except in compliance with the License.
6d8012181SPatrick Venture  * You may obtain a copy of the License at
7d8012181SPatrick Venture  *
8d8012181SPatrick Venture  *     http://www.apache.org/licenses/LICENSE-2.0
9d8012181SPatrick Venture  *
10d8012181SPatrick Venture  * Unless required by applicable law or agreed to in writing, software
11d8012181SPatrick Venture  * distributed under the License is distributed on an "AS IS" BASIS,
12d8012181SPatrick Venture  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d8012181SPatrick Venture  * See the License for the specific language governing permissions and
14d8012181SPatrick Venture  * limitations under the License.
15d8012181SPatrick Venture  */
16*9f1532ddSJonico Eustaquio #include "config.h"
17d8012181SPatrick Venture 
18d8012181SPatrick Venture #include "fancontroller.hpp"
19da4a5dd1SPatrick Venture 
20c32e3fc5SPatrick Venture #include "tuning.hpp"
21d8012181SPatrick Venture #include "util.hpp"
22d8012181SPatrick Venture #include "zone.hpp"
23d8012181SPatrick Venture 
24da4a5dd1SPatrick Venture #include <algorithm>
25ca791156SJosh Lehan #include <cmath>
26da4a5dd1SPatrick Venture #include <iostream>
27da4a5dd1SPatrick Venture 
28a076487aSPatrick Venture namespace pid_control
29a076487aSPatrick Venture {
30a076487aSPatrick Venture 
31da4a5dd1SPatrick Venture std::unique_ptr<PIDController>
createFanPid(ZoneInterface * owner,const std::string & id,const std::vector<std::string> & inputs,const ec::pidinfo & initial)32563a356fSPatrick Venture     FanController::createFanPid(ZoneInterface* owner, const std::string& id,
334a2dc4d8SPatrick Venture                                 const std::vector<std::string>& inputs,
34f77d5a57SPatrick Venture                                 const ec::pidinfo& initial)
35d8012181SPatrick Venture {
36566a1518SPatrick Venture     if (inputs.size() == 0)
37566a1518SPatrick Venture     {
38566a1518SPatrick Venture         return nullptr;
39566a1518SPatrick Venture     }
40d8012181SPatrick Venture     auto fan = std::make_unique<FanController>(id, inputs, owner);
41563a356fSPatrick Venture     ec::pid_info_t* info = fan->getPIDInfo();
42d8012181SPatrick Venture 
437af157b1SPatrick Venture     initializePIDStruct(info, initial);
44d8012181SPatrick Venture 
45d8012181SPatrick Venture     return fan;
46d8012181SPatrick Venture }
47d8012181SPatrick Venture 
inputProc(void)485f59c0fdSPatrick Venture double FanController::inputProc(void)
49d8012181SPatrick Venture {
50d38ae279SJosh Lehan     double value = 0.0;
51d38ae279SJosh Lehan     std::vector<double> values;
52d38ae279SJosh Lehan     std::vector<double>::iterator result;
53d8012181SPatrick Venture 
54d8012181SPatrick Venture     try
55d8012181SPatrick Venture     {
56d8012181SPatrick Venture         for (const auto& name : _inputs)
57d8012181SPatrick Venture         {
58d38ae279SJosh Lehan             // Read the unscaled value, to correctly recover the RPM
59d38ae279SJosh Lehan             value = _owner->getCachedValues(name).unscaled;
60d38ae279SJosh Lehan 
61d8012181SPatrick Venture             /* If we have a fan we can't read, its value will be 0 for at least
62d8012181SPatrick Venture              * some boards, while others... the fan will drop off dbus (if
63d8012181SPatrick Venture              * that's how it's being read and in that case its value will never
64d8012181SPatrick Venture              * be updated anymore, which is relatively harmless, except, when
65d8012181SPatrick Venture              * something tries to read its value through IPMI, and can't, they
66d8012181SPatrick Venture              * sort of have to guess -- all the other fans are reporting, why
67d8012181SPatrick Venture              * not this one?  Maybe it's unable to be read, so it's "bad."
68d8012181SPatrick Venture              */
69ca791156SJosh Lehan             if (!(std::isfinite(value)))
70d8012181SPatrick Venture             {
71ca791156SJosh Lehan                 continue;
72d8012181SPatrick Venture             }
73d38ae279SJosh Lehan             if (value <= 0.0)
74ca791156SJosh Lehan             {
75ca791156SJosh Lehan                 continue;
76ca791156SJosh Lehan             }
77ca791156SJosh Lehan 
78ca791156SJosh Lehan             values.push_back(value);
79d8012181SPatrick Venture         }
80d8012181SPatrick Venture     }
81d8012181SPatrick Venture     catch (const std::exception& e)
82d8012181SPatrick Venture     {
83563a356fSPatrick Venture         std::cerr << "exception on inputProc.\n";
84d8012181SPatrick Venture         throw;
85d8012181SPatrick Venture     }
86d8012181SPatrick Venture 
87566a1518SPatrick Venture     /* Reset the value from the above loop. */
88d38ae279SJosh Lehan     value = 0.0;
89d8012181SPatrick Venture     if (values.size() > 0)
90d8012181SPatrick Venture     {
91d8012181SPatrick Venture         /* the fan PID algorithm was unstable with average, and seemed to work
92d8012181SPatrick Venture          * better with minimum.  I had considered making this choice a variable
93df766f25SPatrick Venture          * in the configuration, and it's a nice-to-have..
94d8012181SPatrick Venture          */
95d8012181SPatrick Venture         result = std::min_element(values.begin(), values.end());
96d8012181SPatrick Venture         value = *result;
97d8012181SPatrick Venture     }
98d8012181SPatrick Venture 
995f59c0fdSPatrick Venture     return value;
100d8012181SPatrick Venture }
101d8012181SPatrick Venture 
setptProc(void)1025f59c0fdSPatrick Venture double FanController::setptProc(void)
103d8012181SPatrick Venture {
104f7a2dd5cSPatrick Venture     double maxRPM = _owner->getMaxSetPointRequest();
105d8012181SPatrick Venture 
106d8012181SPatrick Venture     // store for reference, and check if more or less.
1075f59c0fdSPatrick Venture     double prev = getSetpoint();
108d8012181SPatrick Venture 
109d8012181SPatrick Venture     if (maxRPM > prev)
110d8012181SPatrick Venture     {
111d8012181SPatrick Venture         setFanDirection(FanSpeedDirection::UP);
112d8012181SPatrick Venture     }
113d8012181SPatrick Venture     else if (prev > maxRPM)
114d8012181SPatrick Venture     {
115d8012181SPatrick Venture         setFanDirection(FanSpeedDirection::DOWN);
116d8012181SPatrick Venture     }
117d8012181SPatrick Venture     else
118d8012181SPatrick Venture     {
119d8012181SPatrick Venture         setFanDirection(FanSpeedDirection::NEUTRAL);
120d8012181SPatrick Venture     }
121d8012181SPatrick Venture 
122563a356fSPatrick Venture     setSetpoint(maxRPM);
123d8012181SPatrick Venture 
124d8012181SPatrick Venture     return (maxRPM);
125d8012181SPatrick Venture }
126d8012181SPatrick Venture 
outputProc(double value)1275f59c0fdSPatrick Venture void FanController::outputProc(double value)
128d8012181SPatrick Venture {
1295f59c0fdSPatrick Venture     double percent = value;
130d8012181SPatrick Venture 
131de79ee05SPatrick Venture     /* If doing tuning, don't go into failsafe mode. */
132de79ee05SPatrick Venture     if (!tuningEnabled)
133c32e3fc5SPatrick Venture     {
134df597657SJosh Lehan         bool failsafeCurrState = _owner->getFailSafeMode();
135df597657SJosh Lehan 
136df597657SJosh Lehan         // Note when failsafe state transitions happen
137df597657SJosh Lehan         if (failsafePrevState != failsafeCurrState)
138df597657SJosh Lehan         {
139df597657SJosh Lehan             failsafePrevState = failsafeCurrState;
140df597657SJosh Lehan             failsafeTransition = true;
141df597657SJosh Lehan         }
142df597657SJosh Lehan 
143df597657SJosh Lehan         if (failsafeCurrState)
144d8012181SPatrick Venture         {
145bcdeb83cSBrandon Kim             double failsafePercent = _owner->getFailSafePercent();
146bcdeb83cSBrandon Kim 
147bcdeb83cSBrandon Kim #ifdef STRICT_FAILSAFE_PWM
148bcdeb83cSBrandon Kim             // Unconditionally replace the computed PWM with the
149bcdeb83cSBrandon Kim             // failsafe PWM if STRICT_FAILSAFE_PWM is defined.
150bcdeb83cSBrandon Kim             percent = failsafePercent;
151bcdeb83cSBrandon Kim #else
152bcdeb83cSBrandon Kim             // Ensure PWM is never lower than the failsafe PWM.
153bcdeb83cSBrandon Kim             // The computed PWM is still allowed to rise higher than
154bcdeb83cSBrandon Kim             // failsafe PWM if STRICT_FAILSAFE_PWM is NOT defined.
155bcdeb83cSBrandon Kim             // This is the default behavior.
156bcdeb83cSBrandon Kim             if (percent < failsafePercent)
157d8012181SPatrick Venture             {
158bcdeb83cSBrandon Kim                 percent = failsafePercent;
159d8012181SPatrick Venture             }
160bcdeb83cSBrandon Kim #endif
161d8012181SPatrick Venture         }
162df597657SJosh Lehan 
163df597657SJosh Lehan         // Always print if debug enabled
164df597657SJosh Lehan         if (debugEnabled)
165df597657SJosh Lehan         {
166df597657SJosh Lehan             std::cerr << "Zone " << _owner->getZoneID() << " fans, "
167df597657SJosh Lehan                       << (failsafeCurrState ? "failsafe" : "normal")
168df597657SJosh Lehan                       << " mode, output pwm: " << percent << "\n";
169df597657SJosh Lehan         }
170c51ba919SBonnie Lo         else
171c51ba919SBonnie Lo         {
172df597657SJosh Lehan             // Only print once per transition when not debugging
173df597657SJosh Lehan             if (failsafeTransition)
174df597657SJosh Lehan             {
175df597657SJosh Lehan                 failsafeTransition = false;
176df597657SJosh Lehan                 std::cerr << "Zone " << _owner->getZoneID() << " fans, "
177df597657SJosh Lehan                           << (failsafeCurrState ? "entering failsafe"
178df597657SJosh Lehan                                                 : "returning to normal")
179df597657SJosh Lehan                           << " mode, output pwm: " << percent << "\n";
180df597657SJosh Lehan             }
181df597657SJosh Lehan         }
182df597657SJosh Lehan     }
183df597657SJosh Lehan     else
184df597657SJosh Lehan     {
185c51ba919SBonnie Lo         if (debugEnabled)
186c51ba919SBonnie Lo         {
187c51ba919SBonnie Lo             std::cerr << "Zone " << _owner->getZoneID()
188df597657SJosh Lehan                       << " fans, tuning mode, bypassing failsafe, output pwm: "
189df597657SJosh Lehan                       << percent << "\n";
190c51ba919SBonnie Lo         }
191c32e3fc5SPatrick Venture     }
192d8012181SPatrick Venture 
193d8012181SPatrick Venture     // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
194d38ae279SJosh Lehan     percent /= 100.0;
195d8012181SPatrick Venture 
196d8012181SPatrick Venture     // PidSensorMap for writing.
1974a2dc4d8SPatrick Venture     for (const auto& it : _inputs)
198d8012181SPatrick Venture     {
199a58197cfSPatrick Venture         auto sensor = _owner->getSensor(it);
200a4146eb1SJosh Lehan         auto redundantWrite = _owner->getRedundantWrite();
2013f0f7bc3SJosh Lehan         int64_t rawWritten = -1;
202a4146eb1SJosh Lehan         sensor->write(percent, redundantWrite, &rawWritten);
203b300575eSJosh Lehan 
204b300575eSJosh Lehan         // The outputCache will be used later,
205b300575eSJosh Lehan         // to store a record of the PWM commanded,
206b300575eSJosh Lehan         // so that this information can be included during logging.
207b300575eSJosh Lehan         auto unscaledWritten = static_cast<double>(rawWritten);
208b300575eSJosh Lehan         _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
209d8012181SPatrick Venture     }
210d8012181SPatrick Venture 
211d8012181SPatrick Venture     return;
212d8012181SPatrick Venture }
213a076487aSPatrick Venture 
~FanController()2147e63502aSPatrick Rudolph FanController::~FanController()
2157e63502aSPatrick Rudolph {
2167e63502aSPatrick Rudolph #ifdef OFFLINE_FAILSAFE_PWM
2177e63502aSPatrick Rudolph     double percent = _owner->getFailSafePercent();
2187e63502aSPatrick Rudolph     if (debugEnabled)
2197e63502aSPatrick Rudolph     {
2207e63502aSPatrick Rudolph         std::cerr << "Zone " << _owner->getZoneID()
2217e63502aSPatrick Rudolph                   << " offline fans output pwm: " << percent << "\n";
2227e63502aSPatrick Rudolph     }
2237e63502aSPatrick Rudolph 
2247e63502aSPatrick Rudolph     // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
2257e63502aSPatrick Rudolph     percent /= 100.0;
2267e63502aSPatrick Rudolph 
2277e63502aSPatrick Rudolph     // PidSensorMap for writing.
2287e63502aSPatrick Rudolph     for (const auto& it : _inputs)
2297e63502aSPatrick Rudolph     {
2307e63502aSPatrick Rudolph         auto sensor = _owner->getSensor(it);
2317e63502aSPatrick Rudolph         auto redundantWrite = _owner->getRedundantWrite();
2327e63502aSPatrick Rudolph         int64_t rawWritten;
2337e63502aSPatrick Rudolph         sensor->write(percent, redundantWrite, &rawWritten);
2347e63502aSPatrick Rudolph 
2357e63502aSPatrick Rudolph         // The outputCache will be used later,
2367e63502aSPatrick Rudolph         // to store a record of the PWM commanded,
2377e63502aSPatrick Rudolph         // so that this information can be included during logging.
2387e63502aSPatrick Rudolph         auto unscaledWritten = static_cast<double>(rawWritten);
2397e63502aSPatrick Rudolph         _owner->setOutputCache(sensor->getName(), {percent, unscaledWritten});
2407e63502aSPatrick Rudolph     }
2417e63502aSPatrick Rudolph #endif
2427e63502aSPatrick Rudolph }
2437e63502aSPatrick Rudolph 
244a076487aSPatrick Venture } // namespace pid_control
245