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