xref: /openbmc/dbus-sensors/src/PwmSensor.cpp (revision 3746c553)
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 == 0U)
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.0 * (static_cast<double>(getInt) / pwmMax);
100             curVal = getScaled;
101             controlInterface->signal_property("Target");
102             sensorInterface->signal_property("Value");
103         }
104         return curVal;
105     });
106     // pwm sensor interface is in percent
107     sensorInterface->register_property("MaxValue", static_cast<double>(100));
108     sensorInterface->register_property("MinValue", static_cast<double>(0));
109     sensorInterface->register_property("Unit", sensor_paths::unitPercent);
110 
111     controlInterface = objectServer.add_interface(
112         "/xyz/openbmc_project/control/fanpwm/" + name,
113         "xyz.openbmc_project.Control.FanPwm");
114     controlInterface->register_property(
115         "Target", static_cast<uint64_t>(pwmValue),
116         [this](const uint64_t& req, uint64_t& resp) {
117         if (req > static_cast<uint64_t>(targetIfaceMax))
118         {
119             throw std::runtime_error("Value out of range");
120             return -1;
121         }
122         if (req == resp)
123         {
124             return 1;
125         }
126         auto scaledValue = static_cast<double>(req) / targetIfaceMax;
127         auto roundValue = std::round(scaledValue * pwmMax);
128         setValue(static_cast<uint32_t>(roundValue));
129         resp = req;
130 
131         sensorInterface->signal_property("Value");
132 
133         return 1;
134         },
135         [this](uint64_t& curVal) {
136         auto getInt = getValue();
137         auto scaledValue = static_cast<double>(getInt) / pwmMax;
138         auto roundValue = std::round(scaledValue * targetIfaceMax);
139         auto value = static_cast<uint64_t>(roundValue);
140         if (curVal != value)
141         {
142             curVal = value;
143             controlInterface->signal_property("Target");
144             sensorInterface->signal_property("Value");
145         }
146         return curVal;
147     });
148 
149     sensorInterface->initialize();
150     controlInterface->initialize();
151 
152     if (isValueMutable)
153     {
154         valueMutabilityInterface =
155             std::make_shared<sdbusplus::asio::dbus_interface>(
156                 conn, sensorInterface->get_object_path(),
157                 valueMutabilityInterfaceName);
158         valueMutabilityInterface->register_property("Mutable", true);
159         if (!valueMutabilityInterface->initialize())
160         {
161             std::cerr
162                 << "error initializing sensor value mutability interface\n";
163             valueMutabilityInterface = nullptr;
164         }
165     }
166 
167     association = objectServer.add_interface(
168         "/xyz/openbmc_project/sensors/fan_pwm/" + name, association::interface);
169 
170     // PowerSupply sensors should be associated with chassis board path
171     // and inventory along with psu object.
172     if (sensorType == "PSU")
173     {
174         createInventoryAssoc(conn, association, sensorConfiguration);
175     }
176     else
177     {
178         createAssociation(association, sensorConfiguration);
179     }
180 }
181 PwmSensor::~PwmSensor()
182 {
183     objectServer.remove_interface(sensorInterface);
184     objectServer.remove_interface(controlInterface);
185     objectServer.remove_interface(association);
186 }
187 
188 void PwmSensor::setValue(uint32_t value)
189 {
190     std::ofstream ref(sysPath);
191     if (!ref.good())
192     {
193         throw std::runtime_error("Bad Write File");
194     }
195     ref << value;
196 }
197 
198 // on success returns pwm, on failure throws except on initialization, where it
199 // prints an error and returns 0
200 uint32_t PwmSensor::getValue(bool errThrow)
201 {
202     std::ifstream ref(sysPath);
203     if (!ref.good())
204     {
205         return -1;
206     }
207     std::string line;
208     if (!std::getline(ref, line))
209     {
210         return -1;
211     }
212     try
213     {
214         uint32_t value = std::stoi(line);
215         return value;
216     }
217     catch (const std::invalid_argument&)
218     {
219         std::cerr << "Error reading pwm at " << sysPath << "\n";
220         // throw if not initial read to be caught by dbus bindings
221         if (errThrow)
222         {
223             throw std::runtime_error("Bad Read");
224         }
225     }
226     return 0;
227 }
228