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