xref: /openbmc/dbus-sensors/src/PwmSensor.cpp (revision 5431c49a)
1 /*
2 // Copyright (c) 2018 Intel Corporation
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 #include <PwmSensor.hpp>
17 #include <Utils.hpp>
18 #include <sdbusplus/asio/object_server.hpp>
19 
20 #include <fstream>
21 #include <iostream>
22 #include <stdexcept>
23 #include <string>
24 
25 static constexpr double sysPwmMax = 255.0;
26 static constexpr double psuPwmMax = 100.0;
27 static constexpr double defaultPwm = 30.0;
28 static constexpr double targetIfaceMax = sysPwmMax;
29 
30 PwmSensor::PwmSensor(const std::string& name, const std::string& sysPath,
31                      std::shared_ptr<sdbusplus::asio::connection>& conn,
32                      sdbusplus::asio::object_server& objectServer,
33                      const std::string& sensorConfiguration,
34                      const std::string& sensorType, bool isValueMutable) :
35     sysPath(sysPath),
36     objectServer(objectServer), name(name)
37 {
38     // add interface under sensor and Control.FanPwm as Control is used
39     // in obmc project, also add sensor so it can be viewed as a sensor
40     sensorInterface = objectServer.add_interface(
41         "/xyz/openbmc_project/sensors/fan_pwm/" + name,
42         "xyz.openbmc_project.Sensor.Value");
43     uint32_t pwmValue = getValue(false);
44     if (sensorType == "PSU")
45     {
46         pwmMax = psuPwmMax;
47     }
48     else
49     {
50         pwmMax = sysPwmMax;
51     }
52 
53     if (!pwmValue)
54     {
55         // default pwm to non 0
56         pwmValue = static_cast<uint32_t>(pwmMax * (defaultPwm / 100.0));
57         setValue(pwmValue);
58     }
59     double fValue = 100.0 * (static_cast<double>(pwmValue) / pwmMax);
60     sensorInterface->register_property(
61         "Value", fValue,
62         [this](const double& req, double& resp) {
63             if (!std::isfinite(req))
64             {
65                 // Reject attempted change, if to NaN or other non-numeric
66                 return -1;
67             }
68             if (req > 100.0 || req < 0.0)
69             {
70                 // TODO(): It does not seem desirable to halt daemon here,
71                 // probably should just reject the change, continue running?
72                 throw std::runtime_error("Value out of range");
73                 return -1;
74             }
75 
76             double reqValue = (req / 100.0) * pwmMax;
77             double respValue = (resp / 100.0) * pwmMax;
78             auto reqInt = static_cast<uint32_t>(std::round(reqValue));
79             auto respInt = static_cast<uint32_t>(std::round(respValue));
80             // Avoid floating-point equality, compare as integers
81             if (reqInt == respInt)
82             {
83                 return 1;
84             }
85             setValue(reqInt);
86             resp = req;
87 
88             controlInterface->signal_property("Target");
89 
90             return 1;
91         },
92         [this](double& curVal) {
93             double currScaled = (curVal / 100.0) * pwmMax;
94             auto currInt = static_cast<uint32_t>(std::round(currScaled));
95             auto getInt = getValue();
96             // Avoid floating-point equality, compare as integers
97             if (currInt != getInt)
98             {
99                 double getScaled =
100                     100.0 * (static_cast<double>(getInt) / pwmMax);
101                 curVal = getScaled;
102                 controlInterface->signal_property("Target");
103                 sensorInterface->signal_property("Value");
104             }
105             return curVal;
106         });
107     // pwm sensor interface is in percent
108     sensorInterface->register_property("MaxValue", static_cast<int64_t>(100));
109     sensorInterface->register_property("MinValue", static_cast<int64_t>(0));
110     sensorInterface->register_property("Unit", sensor_paths::unitPercent);
111 
112     controlInterface = objectServer.add_interface(
113         "/xyz/openbmc_project/control/fanpwm/" + name,
114         "xyz.openbmc_project.Control.FanPwm");
115     controlInterface->register_property(
116         "Target", static_cast<uint64_t>(pwmValue),
117         [this](const uint64_t& req, uint64_t& resp) {
118             if (req > static_cast<uint64_t>(targetIfaceMax))
119             {
120                 throw std::runtime_error("Value out of range");
121                 return -1;
122             }
123             if (req == resp)
124             {
125                 return 1;
126             }
127             auto scaledValue = static_cast<double>(req) / targetIfaceMax;
128             auto roundValue = std::round(scaledValue * pwmMax);
129             setValue(static_cast<uint32_t>(roundValue));
130             resp = req;
131 
132             sensorInterface->signal_property("Value");
133 
134             return 1;
135         },
136         [this](uint64_t& curVal) {
137             auto getInt = getValue();
138             auto scaledValue = static_cast<double>(getInt) / pwmMax;
139             auto roundValue = std::round(scaledValue * targetIfaceMax);
140             auto value = static_cast<uint64_t>(roundValue);
141             if (curVal != value)
142             {
143                 curVal = value;
144                 controlInterface->signal_property("Target");
145                 sensorInterface->signal_property("Value");
146             }
147             return curVal;
148         });
149 
150     sensorInterface->initialize();
151     controlInterface->initialize();
152 
153     if (isValueMutable)
154     {
155         valueMutabilityInterface =
156             std::make_shared<sdbusplus::asio::dbus_interface>(
157                 conn, sensorInterface->get_object_path(),
158                 valueMutabilityInterfaceName);
159         valueMutabilityInterface->register_property("Mutable", true);
160         if (!valueMutabilityInterface->initialize())
161         {
162             std::cerr
163                 << "error initializing sensor value mutability interface\n";
164             valueMutabilityInterface = nullptr;
165         }
166     }
167 
168     association = objectServer.add_interface(
169         "/xyz/openbmc_project/sensors/fan_pwm/" + name, association::interface);
170 
171     // PowerSupply sensors should be associated with chassis board path
172     // and inventory along with psu object.
173     if (sensorType == "PSU")
174     {
175         createInventoryAssoc(conn, association, sensorConfiguration);
176     }
177     else
178     {
179         createAssociation(association, sensorConfiguration);
180     }
181 }
182 PwmSensor::~PwmSensor()
183 {
184     objectServer.remove_interface(sensorInterface);
185     objectServer.remove_interface(controlInterface);
186     objectServer.remove_interface(association);
187 }
188 
189 void PwmSensor::setValue(uint32_t value)
190 {
191     std::ofstream ref(sysPath);
192     if (!ref.good())
193     {
194         throw std::runtime_error("Bad Write File");
195     }
196     ref << value;
197 }
198 
199 // on success returns pwm, on failure throws except on initialization, where it
200 // prints an error and returns 0
201 uint32_t PwmSensor::getValue(bool errThrow)
202 {
203     std::ifstream ref(sysPath);
204     if (!ref.good())
205     {
206         return -1;
207     }
208     std::string line;
209     if (!std::getline(ref, line))
210     {
211         return -1;
212     }
213     try
214     {
215         uint32_t value = std::stoi(line);
216         return value;
217     }
218     catch (const std::invalid_argument&)
219     {
220         std::cerr << "Error reading pwm at " << sysPath << "\n";
221         // throw if not initial read to be caught by dbus bindings
222         if (errThrow)
223         {
224             throw std::runtime_error("Bad Read");
225         }
226     }
227     return 0;
228 }
229