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