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