1 /**
2  * Copyright © 2021 IBM 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 "dbus_sensor.hpp"
18 
19 #include <cmath>
20 #include <limits>
21 #include <utility>
22 
23 namespace phosphor::power::regulators
24 {
25 
26 /**
27  * Constants for current sensors.
28  *
29  * Values are in amperes.
30  */
31 constexpr double currentMinValue = 0.0;
32 constexpr double currentMaxValue = 500.0;
33 constexpr double currentHysteresis = 1.0;
34 constexpr const char* currentNamespace = "current";
35 
36 /**
37  * Constants for power sensors.
38  *
39  * Values are in watts.
40  */
41 constexpr double powerMinValue = 0.0;
42 constexpr double powerMaxValue = 1000.0;
43 constexpr double powerHysteresis = 1.0;
44 constexpr const char* powerNamespace = "power";
45 
46 /**
47  * Constants for temperature sensors.
48  *
49  * Values are in degrees Celsius.
50  */
51 constexpr double temperatureMinValue = -50.0;
52 constexpr double temperatureMaxValue = 250.0;
53 constexpr double temperatureHysteresis = 1.0;
54 constexpr const char* temperatureNamespace = "temperature";
55 
56 /**
57  * Constants for voltage sensors.
58  *
59  * Values are in volts.
60  *
61  * Note the hysteresis value is very low.  Small voltage changes can have a big
62  * impact in some systems.  The sensors need to reflect these small changes.
63  */
64 constexpr double voltageMinValue = -15.0;
65 constexpr double voltageMaxValue = 15.0;
66 constexpr double voltageHysteresis = 0.001;
67 constexpr const char* voltageNamespace = "voltage";
68 
DBusSensor(sdbusplus::bus_t & bus,const std::string & name,SensorType type,double value,const std::string & rail,const std::string & deviceInventoryPath,const std::string & chassisInventoryPath)69 DBusSensor::DBusSensor(sdbusplus::bus_t& bus, const std::string& name,
70                        SensorType type, double value, const std::string& rail,
71                        const std::string& deviceInventoryPath,
72                        const std::string& chassisInventoryPath) :
73     bus{bus}, name{name}, type{type}, rail{rail}
74 {
75     // Get sensor properties that are based on the sensor type
76     std::string objectPath;
77     Unit unit;
78     double minValue, maxValue;
79     getTypeBasedProperties(objectPath, unit, minValue, maxValue);
80 
81     // Get the D-Bus associations to create for this sensor
82     std::vector<AssocationTuple> associations =
83         getAssociations(deviceInventoryPath, chassisInventoryPath);
84 
85     // Create the sdbusplus object that implements the D-Bus sensor interfaces.
86     // Skip emitting D-Bus signals until the object has been fully created.
87     dbusObject = std::make_unique<DBusSensorObject>(
88         bus, objectPath.c_str(), DBusSensorObject::action::defer_emit);
89 
90     // Set properties of the Value interface
91     constexpr auto skipSignal = true;
92     dbusObject->value(value, skipSignal);
93     dbusObject->maxValue(maxValue, skipSignal);
94     dbusObject->minValue(minValue, skipSignal);
95     dbusObject->unit(unit, skipSignal);
96 
97     // Set properties of the OperationalStatus interface
98     dbusObject->functional(true, skipSignal);
99 
100     // Set properties of the Availability interface
101     dbusObject->available(true, skipSignal);
102 
103     // Set properties on the Association.Definitions interface
104     dbusObject->associations(std::move(associations), skipSignal);
105 
106     // Now emit signal that object has been created
107     dbusObject->emit_object_added();
108 
109     // Set the last update time
110     setLastUpdateTime();
111 }
112 
disable()113 void DBusSensor::disable()
114 {
115     // Set sensor value to NaN
116     setValueToNaN();
117 
118     // Set the sensor to unavailable since it is disabled
119     dbusObject->available(false);
120 
121     // Set the last update time
122     setLastUpdateTime();
123 }
124 
setToErrorState()125 void DBusSensor::setToErrorState()
126 {
127     // Set sensor value to NaN
128     setValueToNaN();
129 
130     // Set the sensor to non-functional since it could not be read
131     dbusObject->functional(false);
132 
133     // Set the last update time
134     setLastUpdateTime();
135 }
136 
setValue(double value)137 void DBusSensor::setValue(double value)
138 {
139     // Update value on D-Bus if necessary
140     if (shouldUpdateValue(value))
141     {
142         dbusObject->value(value);
143     }
144 
145     // Set the sensor to functional since it has a valid value
146     dbusObject->functional(true);
147 
148     // Set the sensor to available since it is not disabled
149     dbusObject->available(true);
150 
151     // Set the last update time
152     setLastUpdateTime();
153 }
154 
155 std::vector<AssocationTuple>
getAssociations(const std::string & deviceInventoryPath,const std::string & chassisInventoryPath)156     DBusSensor::getAssociations(const std::string& deviceInventoryPath,
157                                 const std::string& chassisInventoryPath)
158 {
159     std::vector<AssocationTuple> associations{};
160 
161     // Add an association between the sensor and the chassis.  This is used by
162     // the Redfish support to find all the sensors in a chassis.
163     associations.emplace_back(
164         std::make_tuple("chassis", "all_sensors", chassisInventoryPath));
165 
166     // Add an association between the sensor and the voltage regulator device.
167     // This is used by the Redfish support to find the hardware/inventory item
168     // associated with a sensor.
169     associations.emplace_back(
170         std::make_tuple("inventory", "sensors", deviceInventoryPath));
171 
172     return associations;
173 }
174 
getTypeBasedProperties(std::string & objectPath,Unit & unit,double & minValue,double & maxValue)175 void DBusSensor::getTypeBasedProperties(std::string& objectPath, Unit& unit,
176                                         double& minValue, double& maxValue)
177 {
178     const char* typeNamespace{""};
179     switch (type)
180     {
181         case SensorType::iout:
182             typeNamespace = currentNamespace;
183             unit = Unit::Amperes;
184             minValue = currentMinValue;
185             maxValue = currentMaxValue;
186             updatePolicy = ValueUpdatePolicy::hysteresis;
187             hysteresis = currentHysteresis;
188             break;
189 
190         case SensorType::iout_peak:
191             typeNamespace = currentNamespace;
192             unit = Unit::Amperes;
193             minValue = currentMinValue;
194             maxValue = currentMaxValue;
195             updatePolicy = ValueUpdatePolicy::highest;
196             break;
197 
198         case SensorType::iout_valley:
199             typeNamespace = currentNamespace;
200             unit = Unit::Amperes;
201             minValue = currentMinValue;
202             maxValue = currentMaxValue;
203             updatePolicy = ValueUpdatePolicy::lowest;
204             break;
205 
206         case SensorType::pout:
207             typeNamespace = powerNamespace;
208             unit = Unit::Watts;
209             minValue = powerMinValue;
210             maxValue = powerMaxValue;
211             updatePolicy = ValueUpdatePolicy::hysteresis;
212             hysteresis = powerHysteresis;
213             break;
214 
215         case SensorType::temperature:
216             typeNamespace = temperatureNamespace;
217             unit = Unit::DegreesC;
218             minValue = temperatureMinValue;
219             maxValue = temperatureMaxValue;
220             updatePolicy = ValueUpdatePolicy::hysteresis;
221             hysteresis = temperatureHysteresis;
222             break;
223 
224         case SensorType::temperature_peak:
225             typeNamespace = temperatureNamespace;
226             unit = Unit::DegreesC;
227             minValue = temperatureMinValue;
228             maxValue = temperatureMaxValue;
229             updatePolicy = ValueUpdatePolicy::highest;
230             break;
231 
232         case SensorType::vout:
233             typeNamespace = voltageNamespace;
234             unit = Unit::Volts;
235             minValue = voltageMinValue;
236             maxValue = voltageMaxValue;
237             updatePolicy = ValueUpdatePolicy::hysteresis;
238             hysteresis = voltageHysteresis;
239             break;
240 
241         case SensorType::vout_peak:
242             typeNamespace = voltageNamespace;
243             unit = Unit::Volts;
244             minValue = voltageMinValue;
245             maxValue = voltageMaxValue;
246             updatePolicy = ValueUpdatePolicy::highest;
247             break;
248 
249         case SensorType::vout_valley:
250         default:
251             typeNamespace = voltageNamespace;
252             unit = Unit::Volts;
253             minValue = voltageMinValue;
254             maxValue = voltageMaxValue;
255             updatePolicy = ValueUpdatePolicy::lowest;
256             break;
257     }
258 
259     // Build object path
260     objectPath = sensorsObjectPath;
261     objectPath += '/';
262     objectPath += typeNamespace;
263     objectPath += '/';
264     objectPath += name;
265 }
266 
setValueToNaN()267 void DBusSensor::setValueToNaN()
268 {
269     // Get current value published on D-Bus
270     double currentValue = dbusObject->value();
271 
272     // Check if current value is already NaN.  We want to avoid an unnecessary
273     // PropertiesChanged signal.  The generated C++ code for the Value interface
274     // does check whether the new value is different from the old one.  However,
275     // it uses the equality operator, and NaN always returns false when compared
276     // to another NaN value.
277     if (!std::isnan(currentValue))
278     {
279         // Set value to NaN
280         dbusObject->value(std::numeric_limits<double>::quiet_NaN());
281     }
282 }
283 
shouldUpdateValue(double value)284 bool DBusSensor::shouldUpdateValue(double value)
285 {
286     // Initially assume we should update the value
287     bool shouldUpdate{true};
288 
289     // Get current value published on D-Bus
290     double currentValue = dbusObject->value();
291 
292     // Update sensor if the current value is NaN.  This indicates it was
293     // disabled or in an error state.  Note: you cannot compare a variable to
294     // NaN directly using the equality operator; it will always return false.
295     if (std::isnan(currentValue))
296     {
297         shouldUpdate = true;
298     }
299     else
300     {
301         // Determine whether to update based on policy used by this sensor
302         switch (updatePolicy)
303         {
304             case ValueUpdatePolicy::hysteresis:
305                 shouldUpdate = (std::abs(value - currentValue) >= hysteresis);
306                 break;
307             case ValueUpdatePolicy::highest:
308                 shouldUpdate = (value > currentValue);
309                 break;
310             case ValueUpdatePolicy::lowest:
311                 shouldUpdate = (value < currentValue);
312                 break;
313         }
314     }
315 
316     return shouldUpdate;
317 }
318 
319 } // namespace phosphor::power::regulators
320