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 <iostream>
25 
26 namespace pid_control
27 {
28 
29 std::unique_ptr<PIDController>
30     FanController::createFanPid(ZoneInterface* owner, const std::string& id,
31                                 const std::vector<std::string>& inputs,
32                                 const ec::pidinfo& initial)
33 {
34     if (inputs.size() == 0)
35     {
36         return nullptr;
37     }
38     auto fan = std::make_unique<FanController>(id, inputs, owner);
39     ec::pid_info_t* info = fan->getPIDInfo();
40 
41     initializePIDStruct(info, initial);
42 
43     return fan;
44 }
45 
46 double FanController::inputProc(void)
47 {
48     double value = 0;
49     std::vector<int64_t> values;
50     std::vector<int64_t>::iterator result;
51 
52     try
53     {
54         for (const auto& name : _inputs)
55         {
56             value = _owner->getCachedValue(name);
57             /* If we have a fan we can't read, its value will be 0 for at least
58              * some boards, while others... the fan will drop off dbus (if
59              * that's how it's being read and in that case its value will never
60              * be updated anymore, which is relatively harmless, except, when
61              * something tries to read its value through IPMI, and can't, they
62              * sort of have to guess -- all the other fans are reporting, why
63              * not this one?  Maybe it's unable to be read, so it's "bad."
64              */
65             if (value > 0)
66             {
67                 values.push_back(value);
68             }
69         }
70     }
71     catch (const std::exception& e)
72     {
73         std::cerr << "exception on inputProc.\n";
74         throw;
75     }
76 
77     /* Reset the value from the above loop. */
78     value = 0;
79     if (values.size() > 0)
80     {
81         /* the fan PID algorithm was unstable with average, and seemed to work
82          * better with minimum.  I had considered making this choice a variable
83          * in the configuration, and it's a nice-to-have..
84          */
85         result = std::min_element(values.begin(), values.end());
86         value = *result;
87     }
88 
89     return value;
90 }
91 
92 double FanController::setptProc(void)
93 {
94     double maxRPM = _owner->getMaxSetPointRequest();
95 
96     // store for reference, and check if more or less.
97     double prev = getSetpoint();
98 
99     if (maxRPM > prev)
100     {
101         setFanDirection(FanSpeedDirection::UP);
102     }
103     else if (prev > maxRPM)
104     {
105         setFanDirection(FanSpeedDirection::DOWN);
106     }
107     else
108     {
109         setFanDirection(FanSpeedDirection::NEUTRAL);
110     }
111 
112     setSetpoint(maxRPM);
113 
114     return (maxRPM);
115 }
116 
117 void FanController::outputProc(double value)
118 {
119     double percent = value;
120 
121     /* If doing tuning, don't go into failsafe mode. */
122     if (!tuningEnabled)
123     {
124         if (_owner->getFailSafeMode())
125         {
126             /* In case it's being set to 100% */
127             if (percent < _owner->getFailSafePercent())
128             {
129                 percent = _owner->getFailSafePercent();
130             }
131         }
132     }
133 
134     // value and kFanFailSafeDutyCycle are 10 for 10% so let's fix that.
135     percent /= 100;
136 
137     // PidSensorMap for writing.
138     for (const auto& it : _inputs)
139     {
140         auto sensor = _owner->getSensor(it);
141         sensor->write(percent);
142     }
143 
144     return;
145 }
146 
147 } // namespace pid_control
148