1*2a40e939SJosh Lehan #include "ExternalSensor.hpp" 2*2a40e939SJosh Lehan #include "Utils.hpp" 3*2a40e939SJosh Lehan #include "VariantVisitors.hpp" 4*2a40e939SJosh Lehan 5*2a40e939SJosh Lehan #include <boost/algorithm/string/predicate.hpp> 6*2a40e939SJosh Lehan #include <boost/algorithm/string/replace.hpp> 7*2a40e939SJosh Lehan #include <boost/container/flat_map.hpp> 8*2a40e939SJosh Lehan #include <boost/container/flat_set.hpp> 9*2a40e939SJosh Lehan #include <sdbusplus/asio/connection.hpp> 10*2a40e939SJosh Lehan #include <sdbusplus/asio/object_server.hpp> 11*2a40e939SJosh Lehan #include <sdbusplus/bus/match.hpp> 12*2a40e939SJosh Lehan 13*2a40e939SJosh Lehan #include <array> 14*2a40e939SJosh Lehan #include <filesystem> 15*2a40e939SJosh Lehan #include <fstream> 16*2a40e939SJosh Lehan #include <functional> 17*2a40e939SJosh Lehan #include <memory> 18*2a40e939SJosh Lehan #include <regex> 19*2a40e939SJosh Lehan #include <stdexcept> 20*2a40e939SJosh Lehan #include <string> 21*2a40e939SJosh Lehan #include <utility> 22*2a40e939SJosh Lehan #include <variant> 23*2a40e939SJosh Lehan #include <vector> 24*2a40e939SJosh Lehan 25*2a40e939SJosh Lehan // Copied from HwmonTempSensor and inspired by 26*2a40e939SJosh Lehan // https://gerrit.openbmc-project.xyz/c/openbmc/dbus-sensors/+/35476 27*2a40e939SJosh Lehan 28*2a40e939SJosh Lehan // The ExternalSensor is a sensor whose value is intended to be writable 29*2a40e939SJosh Lehan // by something external to the BMC, so that the host (or something else) 30*2a40e939SJosh Lehan // can write to it, perhaps by using an IPMI connection. 31*2a40e939SJosh Lehan 32*2a40e939SJosh Lehan // Unlike most other sensors, an external sensor does not correspond 33*2a40e939SJosh Lehan // to a hwmon file or other kernel/hardware interface, 34*2a40e939SJosh Lehan // so, after initialization, this module does not have much to do, 35*2a40e939SJosh Lehan // but it handles reinitialization and thresholds, similar to the others. 36*2a40e939SJosh Lehan 37*2a40e939SJosh Lehan // As there is no corresponding driver or hardware to support, 38*2a40e939SJosh Lehan // all configuration of this sensor comes from the JSON parameters: 39*2a40e939SJosh Lehan // MinValue, MaxValue, PowerState, Measure, Name 40*2a40e939SJosh Lehan 41*2a40e939SJosh Lehan // The purpose of "Measure" is to specify the physical characteristic 42*2a40e939SJosh Lehan // the external sensor is measuring, because with an external sensor 43*2a40e939SJosh Lehan // there is no other way to tell, and it will be used for the object path 44*2a40e939SJosh Lehan // here: /xyz/openbmc_project/sensors/<Measure>/<Name> 45*2a40e939SJosh Lehan 46*2a40e939SJosh Lehan static constexpr bool DEBUG = false; 47*2a40e939SJosh Lehan 48*2a40e939SJosh Lehan static const char* sensorType = 49*2a40e939SJosh Lehan "xyz.openbmc_project.Configuration.ExternalSensor"; 50*2a40e939SJosh Lehan 51*2a40e939SJosh Lehan void createSensors( 52*2a40e939SJosh Lehan boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer, 53*2a40e939SJosh Lehan boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>& 54*2a40e939SJosh Lehan sensors, 55*2a40e939SJosh Lehan std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 56*2a40e939SJosh Lehan const std::shared_ptr<boost::container::flat_set<std::string>>& 57*2a40e939SJosh Lehan sensorsChanged) 58*2a40e939SJosh Lehan { 59*2a40e939SJosh Lehan auto getter = std::make_shared<GetSensorConfiguration>( 60*2a40e939SJosh Lehan dbusConnection, 61*2a40e939SJosh Lehan [&io, &objectServer, &sensors, &dbusConnection, 62*2a40e939SJosh Lehan sensorsChanged](const ManagedObjectType& sensorConfigurations) { 63*2a40e939SJosh Lehan bool firstScan = (sensorsChanged == nullptr); 64*2a40e939SJosh Lehan 65*2a40e939SJosh Lehan for (const std::pair<sdbusplus::message::object_path, SensorData>& 66*2a40e939SJosh Lehan sensor : sensorConfigurations) 67*2a40e939SJosh Lehan { 68*2a40e939SJosh Lehan const std::string& interfacePath = sensor.first.str; 69*2a40e939SJosh Lehan const SensorData& sensorData = sensor.second; 70*2a40e939SJosh Lehan 71*2a40e939SJosh Lehan auto sensorBase = sensorData.find(sensorType); 72*2a40e939SJosh Lehan if (sensorBase == sensorData.end()) 73*2a40e939SJosh Lehan { 74*2a40e939SJosh Lehan std::cerr << "Base configuration not found for " 75*2a40e939SJosh Lehan << interfacePath << "\n"; 76*2a40e939SJosh Lehan continue; 77*2a40e939SJosh Lehan } 78*2a40e939SJosh Lehan 79*2a40e939SJosh Lehan const SensorBaseConfiguration& baseConfiguration = *sensorBase; 80*2a40e939SJosh Lehan const SensorBaseConfigMap& baseConfigMap = 81*2a40e939SJosh Lehan baseConfiguration.second; 82*2a40e939SJosh Lehan 83*2a40e939SJosh Lehan double minValue; 84*2a40e939SJosh Lehan double maxValue; 85*2a40e939SJosh Lehan 86*2a40e939SJosh Lehan // MinValue and MinValue are mandatory numeric parameters 87*2a40e939SJosh Lehan auto minFound = baseConfigMap.find("MinValue"); 88*2a40e939SJosh Lehan if (minFound == baseConfigMap.end()) 89*2a40e939SJosh Lehan { 90*2a40e939SJosh Lehan std::cerr << "MinValue parameter not found for " 91*2a40e939SJosh Lehan << interfacePath << "\n"; 92*2a40e939SJosh Lehan continue; 93*2a40e939SJosh Lehan } 94*2a40e939SJosh Lehan minValue = 95*2a40e939SJosh Lehan std::visit(VariantToDoubleVisitor(), minFound->second); 96*2a40e939SJosh Lehan if (!std::isfinite(minValue)) 97*2a40e939SJosh Lehan { 98*2a40e939SJosh Lehan std::cerr << "MinValue parameter not parsed for " 99*2a40e939SJosh Lehan << interfacePath << "\n"; 100*2a40e939SJosh Lehan continue; 101*2a40e939SJosh Lehan } 102*2a40e939SJosh Lehan 103*2a40e939SJosh Lehan auto maxFound = baseConfigMap.find("MaxValue"); 104*2a40e939SJosh Lehan if (maxFound == baseConfigMap.end()) 105*2a40e939SJosh Lehan { 106*2a40e939SJosh Lehan std::cerr << "MaxValue parameter not found for " 107*2a40e939SJosh Lehan << interfacePath << "\n"; 108*2a40e939SJosh Lehan continue; 109*2a40e939SJosh Lehan } 110*2a40e939SJosh Lehan maxValue = 111*2a40e939SJosh Lehan std::visit(VariantToDoubleVisitor(), maxFound->second); 112*2a40e939SJosh Lehan if (!std::isfinite(maxValue)) 113*2a40e939SJosh Lehan { 114*2a40e939SJosh Lehan std::cerr << "MaxValue parameter not parsed for " 115*2a40e939SJosh Lehan << interfacePath << "\n"; 116*2a40e939SJosh Lehan continue; 117*2a40e939SJosh Lehan } 118*2a40e939SJosh Lehan 119*2a40e939SJosh Lehan std::string sensorName; 120*2a40e939SJosh Lehan std::string sensorMeasure; 121*2a40e939SJosh Lehan 122*2a40e939SJosh Lehan // Name and Measure are mandatory string parameters 123*2a40e939SJosh Lehan auto nameFound = baseConfigMap.find("Name"); 124*2a40e939SJosh Lehan if (nameFound == baseConfigMap.end()) 125*2a40e939SJosh Lehan { 126*2a40e939SJosh Lehan std::cerr << "Name parameter not found for " 127*2a40e939SJosh Lehan << interfacePath << "\n"; 128*2a40e939SJosh Lehan continue; 129*2a40e939SJosh Lehan } 130*2a40e939SJosh Lehan sensorName = 131*2a40e939SJosh Lehan std::visit(VariantToStringVisitor(), nameFound->second); 132*2a40e939SJosh Lehan if (sensorName.empty()) 133*2a40e939SJosh Lehan { 134*2a40e939SJosh Lehan std::cerr << "Name parameter not parsed for " 135*2a40e939SJosh Lehan << interfacePath << "\n"; 136*2a40e939SJosh Lehan continue; 137*2a40e939SJosh Lehan } 138*2a40e939SJosh Lehan 139*2a40e939SJosh Lehan auto measureFound = baseConfigMap.find("Units"); 140*2a40e939SJosh Lehan if (measureFound == baseConfigMap.end()) 141*2a40e939SJosh Lehan { 142*2a40e939SJosh Lehan std::cerr << "Units parameter not found for " 143*2a40e939SJosh Lehan << interfacePath << "\n"; 144*2a40e939SJosh Lehan continue; 145*2a40e939SJosh Lehan } 146*2a40e939SJosh Lehan sensorMeasure = 147*2a40e939SJosh Lehan std::visit(VariantToStringVisitor(), measureFound->second); 148*2a40e939SJosh Lehan if (sensorMeasure.empty()) 149*2a40e939SJosh Lehan { 150*2a40e939SJosh Lehan std::cerr << "Measure parameter not parsed for " 151*2a40e939SJosh Lehan << interfacePath << "\n"; 152*2a40e939SJosh Lehan continue; 153*2a40e939SJosh Lehan } 154*2a40e939SJosh Lehan 155*2a40e939SJosh Lehan // on rescans, only update sensors we were signaled by 156*2a40e939SJosh Lehan auto findSensor = sensors.find(sensorName); 157*2a40e939SJosh Lehan if (!firstScan && (findSensor != sensors.end())) 158*2a40e939SJosh Lehan { 159*2a40e939SJosh Lehan std::string suffixName = "/"; 160*2a40e939SJosh Lehan suffixName += findSensor->second->name; 161*2a40e939SJosh Lehan bool found = false; 162*2a40e939SJosh Lehan for (auto it = sensorsChanged->begin(); 163*2a40e939SJosh Lehan it != sensorsChanged->end(); it++) 164*2a40e939SJosh Lehan { 165*2a40e939SJosh Lehan std::string suffixIt = "/"; 166*2a40e939SJosh Lehan suffixIt += *it; 167*2a40e939SJosh Lehan if (boost::ends_with(suffixIt, suffixName)) 168*2a40e939SJosh Lehan { 169*2a40e939SJosh Lehan sensorsChanged->erase(it); 170*2a40e939SJosh Lehan findSensor->second = nullptr; 171*2a40e939SJosh Lehan found = true; 172*2a40e939SJosh Lehan break; 173*2a40e939SJosh Lehan } 174*2a40e939SJosh Lehan } 175*2a40e939SJosh Lehan if (!found) 176*2a40e939SJosh Lehan { 177*2a40e939SJosh Lehan continue; 178*2a40e939SJosh Lehan } 179*2a40e939SJosh Lehan } 180*2a40e939SJosh Lehan 181*2a40e939SJosh Lehan std::vector<thresholds::Threshold> sensorThresholds; 182*2a40e939SJosh Lehan if (!parseThresholdsFromConfig(sensorData, sensorThresholds)) 183*2a40e939SJosh Lehan { 184*2a40e939SJosh Lehan std::cerr << "error populating thresholds for " 185*2a40e939SJosh Lehan << sensorName << "\n"; 186*2a40e939SJosh Lehan } 187*2a40e939SJosh Lehan 188*2a40e939SJosh Lehan auto findPowerOn = baseConfiguration.second.find("PowerState"); 189*2a40e939SJosh Lehan PowerState readState = PowerState::always; 190*2a40e939SJosh Lehan if (findPowerOn != baseConfiguration.second.end()) 191*2a40e939SJosh Lehan { 192*2a40e939SJosh Lehan std::string powerState = std::visit( 193*2a40e939SJosh Lehan VariantToStringVisitor(), findPowerOn->second); 194*2a40e939SJosh Lehan setReadState(powerState, readState); 195*2a40e939SJosh Lehan } 196*2a40e939SJosh Lehan 197*2a40e939SJosh Lehan auto& sensorEntry = sensors[sensorName]; 198*2a40e939SJosh Lehan sensorEntry = nullptr; 199*2a40e939SJosh Lehan 200*2a40e939SJosh Lehan sensorEntry = std::make_shared<ExternalSensor>( 201*2a40e939SJosh Lehan sensorType, objectServer, dbusConnection, sensorName, 202*2a40e939SJosh Lehan sensorMeasure, std::move(sensorThresholds), interfacePath, 203*2a40e939SJosh Lehan maxValue, minValue, readState); 204*2a40e939SJosh Lehan } 205*2a40e939SJosh Lehan }); 206*2a40e939SJosh Lehan 207*2a40e939SJosh Lehan getter->getConfiguration(std::vector<std::string>{sensorType}); 208*2a40e939SJosh Lehan } 209*2a40e939SJosh Lehan 210*2a40e939SJosh Lehan int main() 211*2a40e939SJosh Lehan { 212*2a40e939SJosh Lehan boost::asio::io_service io; 213*2a40e939SJosh Lehan auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 214*2a40e939SJosh Lehan systemBus->request_name("xyz.openbmc_project.ExternalSensor"); 215*2a40e939SJosh Lehan sdbusplus::asio::object_server objectServer(systemBus); 216*2a40e939SJosh Lehan boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>> 217*2a40e939SJosh Lehan sensors; 218*2a40e939SJosh Lehan std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches; 219*2a40e939SJosh Lehan auto sensorsChanged = 220*2a40e939SJosh Lehan std::make_shared<boost::container::flat_set<std::string>>(); 221*2a40e939SJosh Lehan 222*2a40e939SJosh Lehan io.post([&io, &objectServer, &sensors, &systemBus]() { 223*2a40e939SJosh Lehan createSensors(io, objectServer, sensors, systemBus, nullptr); 224*2a40e939SJosh Lehan }); 225*2a40e939SJosh Lehan 226*2a40e939SJosh Lehan boost::asio::deadline_timer filterTimer(io); 227*2a40e939SJosh Lehan std::function<void(sdbusplus::message::message&)> eventHandler = 228*2a40e939SJosh Lehan [&io, &objectServer, &sensors, &systemBus, &sensorsChanged, 229*2a40e939SJosh Lehan &filterTimer](sdbusplus::message::message& message) { 230*2a40e939SJosh Lehan if (message.is_method_error()) 231*2a40e939SJosh Lehan { 232*2a40e939SJosh Lehan std::cerr << "callback method error\n"; 233*2a40e939SJosh Lehan return; 234*2a40e939SJosh Lehan } 235*2a40e939SJosh Lehan sensorsChanged->insert(message.get_path()); 236*2a40e939SJosh Lehan // this implicitly cancels the timer 237*2a40e939SJosh Lehan filterTimer.expires_from_now(boost::posix_time::seconds(1)); 238*2a40e939SJosh Lehan 239*2a40e939SJosh Lehan filterTimer.async_wait([&io, &objectServer, &sensors, &systemBus, 240*2a40e939SJosh Lehan &sensorsChanged]( 241*2a40e939SJosh Lehan const boost::system::error_code& ec) { 242*2a40e939SJosh Lehan if (ec) 243*2a40e939SJosh Lehan { 244*2a40e939SJosh Lehan if (ec != boost::asio::error::operation_aborted) 245*2a40e939SJosh Lehan { 246*2a40e939SJosh Lehan std::cerr << "callback error: " << ec.message() << "\n"; 247*2a40e939SJosh Lehan } 248*2a40e939SJosh Lehan return; 249*2a40e939SJosh Lehan } 250*2a40e939SJosh Lehan createSensors(io, objectServer, sensors, systemBus, 251*2a40e939SJosh Lehan sensorsChanged); 252*2a40e939SJosh Lehan }); 253*2a40e939SJosh Lehan }; 254*2a40e939SJosh Lehan 255*2a40e939SJosh Lehan auto match = std::make_unique<sdbusplus::bus::match::match>( 256*2a40e939SJosh Lehan static_cast<sdbusplus::bus::bus&>(*systemBus), 257*2a40e939SJosh Lehan "type='signal',member='PropertiesChanged',path_namespace='" + 258*2a40e939SJosh Lehan std::string(inventoryPath) + "',arg0namespace='" + sensorType + "'", 259*2a40e939SJosh Lehan eventHandler); 260*2a40e939SJosh Lehan matches.emplace_back(std::move(match)); 261*2a40e939SJosh Lehan 262*2a40e939SJosh Lehan io.run(); 263*2a40e939SJosh Lehan } 264