blob: 19b8f607afa4e3e5c6156218c30054857cb6c0da [file] [log] [blame]
#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();
}