ExternalSensor: New service for external sensors
This code was mostly copied from HwmonTempSensor, and inspired by the
initial proof of concept from https://gerrit.openbmc-project.xyz/35476
External sensors are designed to be placeholders, with no hwmon or
other hardware support locally. Thus, there is no detection or kernel
driver load, so no change is necessary to entity-manager.
The expectation is that these sensors will be updated by something
external to the BMC, such as the host, which can be useful for
including host-based sensors into phosphor-pid-control thermal zones.
* Added systemd service file, similar to the others.
* Added CMakeLists blocks, similar to the others.
* Also cleaned up some nearby formatting in CMakeLists.
Tested: With the addition of the Mutable feature from 36275, and the
installation of the systemd file from 36272, and a change to our IPMI
daemon (still in the process of being upstreamed), the host can write
to a sensor over IPMI. It correctly shows up in the IPMI SDR, and the
written value correctly shows up on the D-Bus ExternalSensor here.
Here is example JSON for use with entity-manager:
{
"Name": "HostOnlyDevTemp",
"Measure": "temperature",
"MinValue": -10.0,
"MaxValue": 117.5,
"Type": "ExternalSensor"
},
Signed-off-by: Josh Lehan <krellan@google.com>
Change-Id: Id499e1cd6c7918179bb1729e5ce8546cf4121dfa
Signed-off-by: Ed Tanous <edtanous@google.com>
diff --git a/src/ExternalSensorMain.cpp b/src/ExternalSensorMain.cpp
new file mode 100644
index 0000000..9067d7e
--- /dev/null
+++ b/src/ExternalSensorMain.cpp
@@ -0,0 +1,263 @@
+#include "ExternalSensor.hpp"
+#include "Utils.hpp"
+#include "VariantVisitors.hpp"
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/bus/match.hpp>
+
+#include <array>
+#include <filesystem>
+#include <fstream>
+#include <functional>
+#include <memory>
+#include <regex>
+#include <stdexcept>
+#include <string>
+#include <utility>
+#include <variant>
+#include <vector>
+
+// Copied from HwmonTempSensor and inspired by
+// https://gerrit.openbmc-project.xyz/c/openbmc/dbus-sensors/+/35476
+
+// The ExternalSensor is a sensor whose value is intended to be writable
+// by something external to the BMC, so that the host (or something else)
+// can write to it, perhaps by using an IPMI connection.
+
+// Unlike most other sensors, an external sensor does not correspond
+// to a hwmon file or other kernel/hardware interface,
+// so, after initialization, this module does not have much to do,
+// but it handles reinitialization and thresholds, similar to the others.
+
+// As there is no corresponding driver or hardware to support,
+// all configuration of this sensor comes from the JSON parameters:
+// MinValue, MaxValue, PowerState, Measure, Name
+
+// The purpose of "Measure" is to specify the physical characteristic
+// the external sensor is measuring, because with an external sensor
+// there is no other way to tell, and it will be used for the object path
+// here: /xyz/openbmc_project/sensors/<Measure>/<Name>
+
+static constexpr bool DEBUG = false;
+
+static const char* sensorType =
+ "xyz.openbmc_project.Configuration.ExternalSensor";
+
+void createSensors(
+ boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
+ boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
+ sensors,
+ std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+ const std::shared_ptr<boost::container::flat_set<std::string>>&
+ sensorsChanged)
+{
+ auto getter = std::make_shared<GetSensorConfiguration>(
+ dbusConnection,
+ [&io, &objectServer, &sensors, &dbusConnection,
+ sensorsChanged](const ManagedObjectType& sensorConfigurations) {
+ bool firstScan = (sensorsChanged == nullptr);
+
+ for (const std::pair<sdbusplus::message::object_path, SensorData>&
+ sensor : sensorConfigurations)
+ {
+ const std::string& interfacePath = sensor.first.str;
+ const SensorData& sensorData = sensor.second;
+
+ auto sensorBase = sensorData.find(sensorType);
+ if (sensorBase == sensorData.end())
+ {
+ std::cerr << "Base configuration not found for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ const SensorBaseConfiguration& baseConfiguration = *sensorBase;
+ const SensorBaseConfigMap& baseConfigMap =
+ baseConfiguration.second;
+
+ double minValue;
+ double maxValue;
+
+ // MinValue and MinValue are mandatory numeric parameters
+ auto minFound = baseConfigMap.find("MinValue");
+ if (minFound == baseConfigMap.end())
+ {
+ std::cerr << "MinValue parameter not found for "
+ << interfacePath << "\n";
+ continue;
+ }
+ minValue =
+ std::visit(VariantToDoubleVisitor(), minFound->second);
+ if (!std::isfinite(minValue))
+ {
+ std::cerr << "MinValue parameter not parsed for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ auto maxFound = baseConfigMap.find("MaxValue");
+ if (maxFound == baseConfigMap.end())
+ {
+ std::cerr << "MaxValue parameter not found for "
+ << interfacePath << "\n";
+ continue;
+ }
+ maxValue =
+ std::visit(VariantToDoubleVisitor(), maxFound->second);
+ if (!std::isfinite(maxValue))
+ {
+ std::cerr << "MaxValue parameter not parsed for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ std::string sensorName;
+ std::string sensorMeasure;
+
+ // Name and Measure are mandatory string parameters
+ auto nameFound = baseConfigMap.find("Name");
+ if (nameFound == baseConfigMap.end())
+ {
+ std::cerr << "Name parameter not found for "
+ << interfacePath << "\n";
+ continue;
+ }
+ sensorName =
+ std::visit(VariantToStringVisitor(), nameFound->second);
+ if (sensorName.empty())
+ {
+ std::cerr << "Name parameter not parsed for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ auto measureFound = baseConfigMap.find("Units");
+ if (measureFound == baseConfigMap.end())
+ {
+ std::cerr << "Units parameter not found for "
+ << interfacePath << "\n";
+ continue;
+ }
+ sensorMeasure =
+ std::visit(VariantToStringVisitor(), measureFound->second);
+ if (sensorMeasure.empty())
+ {
+ std::cerr << "Measure parameter not parsed for "
+ << interfacePath << "\n";
+ continue;
+ }
+
+ // on rescans, only update sensors we were signaled by
+ auto findSensor = sensors.find(sensorName);
+ if (!firstScan && (findSensor != sensors.end()))
+ {
+ std::string suffixName = "/";
+ suffixName += findSensor->second->name;
+ bool found = false;
+ for (auto it = sensorsChanged->begin();
+ it != sensorsChanged->end(); it++)
+ {
+ std::string suffixIt = "/";
+ suffixIt += *it;
+ if (boost::ends_with(suffixIt, suffixName))
+ {
+ sensorsChanged->erase(it);
+ findSensor->second = nullptr;
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ continue;
+ }
+ }
+
+ std::vector<thresholds::Threshold> sensorThresholds;
+ if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
+ {
+ std::cerr << "error populating thresholds for "
+ << sensorName << "\n";
+ }
+
+ auto findPowerOn = baseConfiguration.second.find("PowerState");
+ PowerState readState = PowerState::always;
+ if (findPowerOn != baseConfiguration.second.end())
+ {
+ std::string powerState = std::visit(
+ VariantToStringVisitor(), findPowerOn->second);
+ setReadState(powerState, readState);
+ }
+
+ auto& sensorEntry = sensors[sensorName];
+ sensorEntry = nullptr;
+
+ sensorEntry = std::make_shared<ExternalSensor>(
+ sensorType, objectServer, dbusConnection, sensorName,
+ sensorMeasure, std::move(sensorThresholds), interfacePath,
+ maxValue, minValue, readState);
+ }
+ });
+
+ getter->getConfiguration(std::vector<std::string>{sensorType});
+}
+
+int main()
+{
+ boost::asio::io_service io;
+ auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+ systemBus->request_name("xyz.openbmc_project.ExternalSensor");
+ sdbusplus::asio::object_server objectServer(systemBus);
+ boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>
+ sensors;
+ std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
+ auto sensorsChanged =
+ std::make_shared<boost::container::flat_set<std::string>>();
+
+ io.post([&io, &objectServer, &sensors, &systemBus]() {
+ createSensors(io, objectServer, sensors, systemBus, nullptr);
+ });
+
+ boost::asio::deadline_timer filterTimer(io);
+ std::function<void(sdbusplus::message::message&)> eventHandler =
+ [&io, &objectServer, &sensors, &systemBus, &sensorsChanged,
+ &filterTimer](sdbusplus::message::message& message) {
+ if (message.is_method_error())
+ {
+ std::cerr << "callback method error\n";
+ return;
+ }
+ sensorsChanged->insert(message.get_path());
+ // this implicitly cancels the timer
+ filterTimer.expires_from_now(boost::posix_time::seconds(1));
+
+ filterTimer.async_wait([&io, &objectServer, &sensors, &systemBus,
+ &sensorsChanged](
+ const boost::system::error_code& ec) {
+ if (ec)
+ {
+ if (ec != boost::asio::error::operation_aborted)
+ {
+ std::cerr << "callback error: " << ec.message() << "\n";
+ }
+ return;
+ }
+ createSensors(io, objectServer, sensors, systemBus,
+ sensorsChanged);
+ });
+ };
+
+ auto match = std::make_unique<sdbusplus::bus::match::match>(
+ static_cast<sdbusplus::bus::bus&>(*systemBus),
+ "type='signal',member='PropertiesChanged',path_namespace='" +
+ std::string(inventoryPath) + "',arg0namespace='" + sensorType + "'",
+ eventHandler);
+ matches.emplace_back(std::move(match));
+
+ io.run();
+}