regulators: Create DBusSensor class
Create the DBusSensor class that represents a voltage regulator sensor
on D-Bus.
Each voltage rail in the system may provide multiple types of sensor
data, such as temperature, output voltage, and output current. A
DBusSensor tracks one of these data types for a voltage rail.
Class supports the following sensor operations:
* Create sensor
* Set sensor value
* Disable sensor (when system is powered off)
* Set sensor to error state (when unable to read value due to I2C error)
* Delete sensor
Note: A different class will implement the ObjectManager interface for
all voltage regulator sensors. That will be in a future commit.
Tested:
* Created a sensor of each supported type (vout, iout, etc.)
* Set the value of a sensor
* Tested update policies: hysteresis, highest, lowest
* Disabled a sensor
* Set a sensor to the error state
* Verified all D-Bus signals emitted
* InterfacesAdded signal emitted with all info when sensor created
* PropertiesChanged signal emitted when sensor properties change
* InterfacesRemoved signal emitted when sensor deleted
* See https://gist.github.com/smccarney/5ae989da1977162ae1a21c208d5302fc
for complete test plan.
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
Change-Id: I655277e6bf0a31a8778a54e87d5864f9951b045e
diff --git a/phosphor-regulators/src/dbus_sensor.cpp b/phosphor-regulators/src/dbus_sensor.cpp
new file mode 100644
index 0000000..1fd4bf6
--- /dev/null
+++ b/phosphor-regulators/src/dbus_sensor.cpp
@@ -0,0 +1,308 @@
+/**
+ * Copyright © 2021 IBM Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dbus_sensor.hpp"
+
+#include <cmath>
+#include <limits>
+#include <utility>
+
+namespace phosphor::power::regulators
+{
+
+/**
+ * Constants for current sensors.
+ *
+ * Values are in amperes.
+ */
+constexpr double currentMinValue = 0.0;
+constexpr double currentMaxValue = 500.0;
+constexpr double currentHysteresis = 1.0;
+constexpr const char* currentNamespace = "current";
+
+/**
+ * Constants for power sensors.
+ *
+ * Values are in watts.
+ */
+constexpr double powerMinValue = 0.0;
+constexpr double powerMaxValue = 1000.0;
+constexpr double powerHysteresis = 1.0;
+constexpr const char* powerNamespace = "power";
+
+/**
+ * Constants for temperature sensors.
+ *
+ * Values are in degrees Celsius.
+ */
+constexpr double temperatureMinValue = -50.0;
+constexpr double temperatureMaxValue = 250.0;
+constexpr double temperatureHysteresis = 1.0;
+constexpr const char* temperatureNamespace = "temperature";
+
+/**
+ * Constants for voltage sensors.
+ *
+ * Values are in volts.
+ *
+ * Note the hysteresis value is very low. Small voltage changes can have a big
+ * impact in some systems. The sensors need to reflect these small changes.
+ */
+constexpr double voltageMinValue = -15.0;
+constexpr double voltageMaxValue = 15.0;
+constexpr double voltageHysteresis = 0.001;
+constexpr const char* voltageNamespace = "voltage";
+
+DBusSensor::DBusSensor(sdbusplus::bus::bus& bus, const std::string& name,
+ SensorType type, double value, const std::string& rail,
+ const std::string& deviceInventoryPath,
+ const std::string& chassisInventoryPath) :
+ bus{bus},
+ name{name}, type{type}, rail{rail}
+{
+ // Get sensor properties that are based on the sensor type
+ std::string objectPath;
+ Unit unit;
+ double minValue, maxValue;
+ getTypeBasedProperties(objectPath, unit, minValue, maxValue);
+
+ // Get the D-Bus associations to create for this sensor
+ std::vector<AssocationTuple> associations =
+ getAssociations(deviceInventoryPath, chassisInventoryPath);
+
+ // Create the sdbusplus object that implements the D-Bus sensor interfaces.
+ // Skip emitting D-Bus signals until the object has been fully created.
+ bool skipSignal{true};
+ dbusObject =
+ std::make_unique<DBusSensorObject>(bus, objectPath.c_str(), skipSignal);
+
+ // Set properties of the Value interface
+ dbusObject->value(value, skipSignal);
+ dbusObject->maxValue(maxValue, skipSignal);
+ dbusObject->minValue(minValue, skipSignal);
+ dbusObject->unit(unit, skipSignal);
+
+ // Set properties of the OperationalStatus interface
+ dbusObject->functional(true, skipSignal);
+
+ // Set properties of the Availability interface
+ dbusObject->available(true, skipSignal);
+
+ // Set properties on the Association.Definitions interface
+ dbusObject->associations(std::move(associations), skipSignal);
+
+ // Now emit signal that object has been created
+ dbusObject->emit_object_added();
+}
+
+void DBusSensor::disable()
+{
+ // Set sensor value to NaN
+ setValueToNaN();
+
+ // Set the sensor to unavailable since it is disabled
+ dbusObject->available(false);
+}
+
+void DBusSensor::setToErrorState()
+{
+ // Set sensor value to NaN
+ setValueToNaN();
+
+ // Set the sensor to non-functional since it could not be read
+ dbusObject->functional(false);
+}
+
+void DBusSensor::setValue(double value)
+{
+ // Update value on D-Bus if necessary
+ if (shouldUpdateValue(value))
+ {
+ dbusObject->value(value);
+ }
+
+ // Set the sensor to functional since it has a valid value
+ dbusObject->functional(true);
+
+ // Set the sensor to available since it is not disabled
+ dbusObject->available(true);
+}
+
+std::vector<AssocationTuple>
+ DBusSensor::getAssociations(const std::string& deviceInventoryPath,
+ const std::string& chassisInventoryPath)
+{
+ std::vector<AssocationTuple> associations{};
+
+ // Add an association between the sensor and the chassis. This is used by
+ // the Redfish support to find all the sensors in a chassis.
+ associations.emplace_back(
+ std::make_tuple("chassis", "all_sensors", chassisInventoryPath));
+
+ // Add an association between the sensor and the voltage regulator device.
+ // This is used by the Redfish support to find the hardware/inventory item
+ // associated with a sensor.
+ associations.emplace_back(
+ std::make_tuple("inventory", "sensors", deviceInventoryPath));
+
+ return associations;
+}
+
+void DBusSensor::getTypeBasedProperties(std::string& objectPath, Unit& unit,
+ double& minValue, double& maxValue)
+{
+ const char* typeNamespace{""};
+ switch (type)
+ {
+ case SensorType::iout:
+ typeNamespace = currentNamespace;
+ unit = Unit::Amperes;
+ minValue = currentMinValue;
+ maxValue = currentMaxValue;
+ updatePolicy = ValueUpdatePolicy::hysteresis;
+ hysteresis = currentHysteresis;
+ break;
+
+ case SensorType::iout_peak:
+ typeNamespace = currentNamespace;
+ unit = Unit::Amperes;
+ minValue = currentMinValue;
+ maxValue = currentMaxValue;
+ updatePolicy = ValueUpdatePolicy::highest;
+ break;
+
+ case SensorType::iout_valley:
+ typeNamespace = currentNamespace;
+ unit = Unit::Amperes;
+ minValue = currentMinValue;
+ maxValue = currentMaxValue;
+ updatePolicy = ValueUpdatePolicy::lowest;
+ break;
+
+ case SensorType::pout:
+ typeNamespace = powerNamespace;
+ unit = Unit::Watts;
+ minValue = powerMinValue;
+ maxValue = powerMaxValue;
+ updatePolicy = ValueUpdatePolicy::hysteresis;
+ hysteresis = powerHysteresis;
+ break;
+
+ case SensorType::temperature:
+ typeNamespace = temperatureNamespace;
+ unit = Unit::DegreesC;
+ minValue = temperatureMinValue;
+ maxValue = temperatureMaxValue;
+ updatePolicy = ValueUpdatePolicy::hysteresis;
+ hysteresis = temperatureHysteresis;
+ break;
+
+ case SensorType::temperature_peak:
+ typeNamespace = temperatureNamespace;
+ unit = Unit::DegreesC;
+ minValue = temperatureMinValue;
+ maxValue = temperatureMaxValue;
+ updatePolicy = ValueUpdatePolicy::highest;
+ break;
+
+ case SensorType::vout:
+ typeNamespace = voltageNamespace;
+ unit = Unit::Volts;
+ minValue = voltageMinValue;
+ maxValue = voltageMaxValue;
+ updatePolicy = ValueUpdatePolicy::hysteresis;
+ hysteresis = voltageHysteresis;
+ break;
+
+ case SensorType::vout_peak:
+ typeNamespace = voltageNamespace;
+ unit = Unit::Volts;
+ minValue = voltageMinValue;
+ maxValue = voltageMaxValue;
+ updatePolicy = ValueUpdatePolicy::highest;
+ break;
+
+ case SensorType::vout_valley:
+ default:
+ typeNamespace = voltageNamespace;
+ unit = Unit::Volts;
+ minValue = voltageMinValue;
+ maxValue = voltageMaxValue;
+ updatePolicy = ValueUpdatePolicy::lowest;
+ break;
+ }
+
+ // Build object path
+ objectPath = sensorsObjectPath;
+ objectPath += '/';
+ objectPath += typeNamespace;
+ objectPath += '/';
+ objectPath += name;
+}
+
+void DBusSensor::setValueToNaN()
+{
+ // Get current value published on D-Bus
+ double currentValue = dbusObject->value();
+
+ // Check if current value is already NaN. We want to avoid an unnecessary
+ // PropertiesChanged signal. The generated C++ code for the Value interface
+ // does check whether the new value is different from the old one. However,
+ // it uses the equality operator, and NaN always returns false when compared
+ // to another NaN value.
+ if (!std::isnan(currentValue))
+ {
+ // Set value to NaN
+ dbusObject->value(std::numeric_limits<double>::quiet_NaN());
+ }
+}
+
+bool DBusSensor::shouldUpdateValue(double value)
+{
+ // Initially assume we should update the value
+ bool shouldUpdate{true};
+
+ // Get current value published on D-Bus
+ double currentValue = dbusObject->value();
+
+ // Update sensor if the current value is NaN. This indicates it was
+ // disabled or in an error state. Note: you cannot compare a variable to
+ // NaN directly using the equality operator; it will always return false.
+ if (std::isnan(currentValue))
+ {
+ shouldUpdate = true;
+ }
+ else
+ {
+ // Determine whether to update based on policy used by this sensor
+ switch (updatePolicy)
+ {
+ case ValueUpdatePolicy::hysteresis:
+ shouldUpdate = (std::abs(value - currentValue) >= hysteresis);
+ break;
+ case ValueUpdatePolicy::highest:
+ shouldUpdate = (value > currentValue);
+ break;
+ case ValueUpdatePolicy::lowest:
+ shouldUpdate = (value < currentValue);
+ break;
+ }
+ }
+
+ return shouldUpdate;
+}
+
+} // namespace phosphor::power::regulators