12a40e939SJosh Lehan #include "ExternalSensor.hpp" 22a40e939SJosh Lehan #include "Utils.hpp" 32a40e939SJosh Lehan #include "VariantVisitors.hpp" 42a40e939SJosh Lehan 52a40e939SJosh Lehan #include <boost/algorithm/string/predicate.hpp> 62a40e939SJosh Lehan #include <boost/algorithm/string/replace.hpp> 72a40e939SJosh Lehan #include <boost/container/flat_map.hpp> 82a40e939SJosh Lehan #include <boost/container/flat_set.hpp> 92a40e939SJosh Lehan #include <sdbusplus/asio/connection.hpp> 102a40e939SJosh Lehan #include <sdbusplus/asio/object_server.hpp> 112a40e939SJosh Lehan #include <sdbusplus/bus/match.hpp> 122a40e939SJosh Lehan 132a40e939SJosh Lehan #include <array> 142a40e939SJosh Lehan #include <filesystem> 152a40e939SJosh Lehan #include <fstream> 162a40e939SJosh Lehan #include <functional> 172a40e939SJosh Lehan #include <memory> 182a40e939SJosh Lehan #include <regex> 192a40e939SJosh Lehan #include <stdexcept> 202a40e939SJosh Lehan #include <string> 212a40e939SJosh Lehan #include <utility> 222a40e939SJosh Lehan #include <variant> 232a40e939SJosh Lehan #include <vector> 242a40e939SJosh Lehan 252a40e939SJosh Lehan // Copied from HwmonTempSensor and inspired by 262a40e939SJosh Lehan // https://gerrit.openbmc-project.xyz/c/openbmc/dbus-sensors/+/35476 272a40e939SJosh Lehan 282a40e939SJosh Lehan // The ExternalSensor is a sensor whose value is intended to be writable 292a40e939SJosh Lehan // by something external to the BMC, so that the host (or something else) 307243217bSJosh Lehan // can write to it, perhaps by using an IPMI or Redfish connection. 312a40e939SJosh Lehan 322a40e939SJosh Lehan // Unlike most other sensors, an external sensor does not correspond 337243217bSJosh Lehan // to a hwmon file or any other kernel/hardware interface, 342a40e939SJosh Lehan // so, after initialization, this module does not have much to do, 352a40e939SJosh Lehan // but it handles reinitialization and thresholds, similar to the others. 367243217bSJosh Lehan // The main work of this module is to provide backing storage for a 377243217bSJosh Lehan // sensor that exists only virtually, and to provide an optional 387243217bSJosh Lehan // timeout service for detecting loss of timely updates. 392a40e939SJosh Lehan 402a40e939SJosh Lehan // As there is no corresponding driver or hardware to support, 412a40e939SJosh Lehan // all configuration of this sensor comes from the JSON parameters: 427243217bSJosh Lehan // MinValue, MaxValue, Timeout, PowerState, Units, Name 432a40e939SJosh Lehan 447243217bSJosh Lehan // The purpose of "Units" is to specify the physical characteristic 452a40e939SJosh Lehan // the external sensor is measuring, because with an external sensor 462a40e939SJosh Lehan // there is no other way to tell, and it will be used for the object path 477243217bSJosh Lehan // here: /xyz/openbmc_project/sensors/<Units>/<Name> 487243217bSJosh Lehan 497243217bSJosh Lehan // For more information, see external-sensor.md design document: 507243217bSJosh Lehan // https://gerrit.openbmc-project.xyz/c/openbmc/docs/+/41452 517243217bSJosh Lehan // https://github.com/openbmc/docs/tree/master/designs/ 522a40e939SJosh Lehan 538a57ec09SEd Tanous static constexpr bool debug = false; 542a40e939SJosh Lehan 552a40e939SJosh Lehan static const char* sensorType = 562a40e939SJosh Lehan "xyz.openbmc_project.Configuration.ExternalSensor"; 572a40e939SJosh Lehan 587243217bSJosh Lehan void updateReaper(boost::container::flat_map< 597243217bSJosh Lehan std::string, std::shared_ptr<ExternalSensor>>& sensors, 607243217bSJosh Lehan boost::asio::steady_timer& timer, 617243217bSJosh Lehan const std::chrono::steady_clock::time_point& now) 627243217bSJosh Lehan { 637243217bSJosh Lehan // First pass, reap all stale sensors 647243217bSJosh Lehan for (auto& sensor : sensors) 657243217bSJosh Lehan { 667243217bSJosh Lehan if (!sensor.second) 677243217bSJosh Lehan { 687243217bSJosh Lehan continue; 697243217bSJosh Lehan } 707243217bSJosh Lehan 717243217bSJosh Lehan if (!sensor.second->isAliveAndPerishable()) 727243217bSJosh Lehan { 737243217bSJosh Lehan continue; 747243217bSJosh Lehan } 757243217bSJosh Lehan 767243217bSJosh Lehan if (!sensor.second->isAliveAndFresh(now)) 777243217bSJosh Lehan { 787243217bSJosh Lehan // Mark sensor as dead, no longer alive 797243217bSJosh Lehan sensor.second->writeInvalidate(); 807243217bSJosh Lehan } 817243217bSJosh Lehan } 827243217bSJosh Lehan 837243217bSJosh Lehan std::chrono::steady_clock::duration nextCheck; 847243217bSJosh Lehan bool needCheck = false; 857243217bSJosh Lehan 867243217bSJosh Lehan // Second pass, determine timer interval to next check 877243217bSJosh Lehan for (auto& sensor : sensors) 887243217bSJosh Lehan { 897243217bSJosh Lehan if (!sensor.second) 907243217bSJosh Lehan { 917243217bSJosh Lehan continue; 927243217bSJosh Lehan } 937243217bSJosh Lehan 947243217bSJosh Lehan if (!sensor.second->isAliveAndPerishable()) 957243217bSJosh Lehan { 967243217bSJosh Lehan continue; 977243217bSJosh Lehan } 987243217bSJosh Lehan 997243217bSJosh Lehan auto expiration = sensor.second->ageRemaining(now); 1007243217bSJosh Lehan 1017243217bSJosh Lehan if (needCheck) 1027243217bSJosh Lehan { 1037243217bSJosh Lehan nextCheck = std::min(nextCheck, expiration); 1047243217bSJosh Lehan } 1057243217bSJosh Lehan else 1067243217bSJosh Lehan { 1077243217bSJosh Lehan // Initialization 1087243217bSJosh Lehan nextCheck = expiration; 1097243217bSJosh Lehan needCheck = true; 1107243217bSJosh Lehan } 1117243217bSJosh Lehan } 1127243217bSJosh Lehan 1137243217bSJosh Lehan if (!needCheck) 1147243217bSJosh Lehan { 1157243217bSJosh Lehan if constexpr (debug) 1167243217bSJosh Lehan { 1177243217bSJosh Lehan std::cerr << "Next ExternalSensor timer idle\n"; 1187243217bSJosh Lehan } 1197243217bSJosh Lehan 1207243217bSJosh Lehan return; 1217243217bSJosh Lehan } 1227243217bSJosh Lehan 1237243217bSJosh Lehan timer.expires_at(now + nextCheck); 1247243217bSJosh Lehan 1257243217bSJosh Lehan timer.async_wait([&sensors, &timer](const boost::system::error_code& err) { 1267243217bSJosh Lehan if (err != boost::system::errc::success) 1277243217bSJosh Lehan { 1287243217bSJosh Lehan // Cancellation is normal, as timer is dynamically rescheduled 1290362738dSJosh Lehan if (err != boost::asio::error::operation_aborted) 1307243217bSJosh Lehan { 1317243217bSJosh Lehan std::cerr << "ExternalSensor timer scheduling problem: " 1327243217bSJosh Lehan << err.message() << "\n"; 1337243217bSJosh Lehan } 1347243217bSJosh Lehan return; 1357243217bSJosh Lehan } 1360362738dSJosh Lehan 1377243217bSJosh Lehan updateReaper(sensors, timer, std::chrono::steady_clock::now()); 1387243217bSJosh Lehan }); 1397243217bSJosh Lehan 1407243217bSJosh Lehan if constexpr (debug) 1417243217bSJosh Lehan { 1427243217bSJosh Lehan std::cerr << "Next ExternalSensor timer " 1437243217bSJosh Lehan << std::chrono::duration_cast<std::chrono::microseconds>( 1447243217bSJosh Lehan nextCheck) 1457243217bSJosh Lehan .count() 1467243217bSJosh Lehan << " us\n"; 1477243217bSJosh Lehan } 1487243217bSJosh Lehan } 1497243217bSJosh Lehan 1502a40e939SJosh Lehan void createSensors( 151*8a17c303SEd Tanous sdbusplus::asio::object_server& objectServer, 1522a40e939SJosh Lehan boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>& 1532a40e939SJosh Lehan sensors, 1542a40e939SJosh Lehan std::shared_ptr<sdbusplus::asio::connection>& dbusConnection, 1552a40e939SJosh Lehan const std::shared_ptr<boost::container::flat_set<std::string>>& 1567243217bSJosh Lehan sensorsChanged, 1577243217bSJosh Lehan boost::asio::steady_timer& reaperTimer) 1582a40e939SJosh Lehan { 1590362738dSJosh Lehan if constexpr (debug) 1600362738dSJosh Lehan { 1610362738dSJosh Lehan std::cerr << "ExternalSensor considering creating sensors\n"; 1620362738dSJosh Lehan } 1630362738dSJosh Lehan 1642a40e939SJosh Lehan auto getter = std::make_shared<GetSensorConfiguration>( 1652a40e939SJosh Lehan dbusConnection, 166*8a17c303SEd Tanous [&objectServer, &sensors, &dbusConnection, sensorsChanged, 1677243217bSJosh Lehan &reaperTimer](const ManagedObjectType& sensorConfigurations) { 1682a40e939SJosh Lehan bool firstScan = (sensorsChanged == nullptr); 1692a40e939SJosh Lehan 1702a40e939SJosh Lehan for (const std::pair<sdbusplus::message::object_path, SensorData>& 1712a40e939SJosh Lehan sensor : sensorConfigurations) 1722a40e939SJosh Lehan { 1732a40e939SJosh Lehan const std::string& interfacePath = sensor.first.str; 1742a40e939SJosh Lehan const SensorData& sensorData = sensor.second; 1752a40e939SJosh Lehan 1762a40e939SJosh Lehan auto sensorBase = sensorData.find(sensorType); 1772a40e939SJosh Lehan if (sensorBase == sensorData.end()) 1782a40e939SJosh Lehan { 1792a40e939SJosh Lehan std::cerr << "Base configuration not found for " 1802a40e939SJosh Lehan << interfacePath << "\n"; 1812a40e939SJosh Lehan continue; 1822a40e939SJosh Lehan } 1832a40e939SJosh Lehan 1842a40e939SJosh Lehan const SensorBaseConfiguration& baseConfiguration = *sensorBase; 1852a40e939SJosh Lehan const SensorBaseConfigMap& baseConfigMap = 1862a40e939SJosh Lehan baseConfiguration.second; 1872a40e939SJosh Lehan 1882a40e939SJosh Lehan double minValue; 1892a40e939SJosh Lehan double maxValue; 1902a40e939SJosh Lehan 1912a40e939SJosh Lehan // MinValue and MinValue are mandatory numeric parameters 1922a40e939SJosh Lehan auto minFound = baseConfigMap.find("MinValue"); 1932a40e939SJosh Lehan if (minFound == baseConfigMap.end()) 1942a40e939SJosh Lehan { 1952a40e939SJosh Lehan std::cerr << "MinValue parameter not found for " 1962a40e939SJosh Lehan << interfacePath << "\n"; 1972a40e939SJosh Lehan continue; 1982a40e939SJosh Lehan } 1992a40e939SJosh Lehan minValue = 2002a40e939SJosh Lehan std::visit(VariantToDoubleVisitor(), minFound->second); 2012a40e939SJosh Lehan if (!std::isfinite(minValue)) 2022a40e939SJosh Lehan { 2032a40e939SJosh Lehan std::cerr << "MinValue parameter not parsed for " 2042a40e939SJosh Lehan << interfacePath << "\n"; 2052a40e939SJosh Lehan continue; 2062a40e939SJosh Lehan } 2072a40e939SJosh Lehan 2082a40e939SJosh Lehan auto maxFound = baseConfigMap.find("MaxValue"); 2092a40e939SJosh Lehan if (maxFound == baseConfigMap.end()) 2102a40e939SJosh Lehan { 2112a40e939SJosh Lehan std::cerr << "MaxValue parameter not found for " 2122a40e939SJosh Lehan << interfacePath << "\n"; 2132a40e939SJosh Lehan continue; 2142a40e939SJosh Lehan } 2152a40e939SJosh Lehan maxValue = 2162a40e939SJosh Lehan std::visit(VariantToDoubleVisitor(), maxFound->second); 2172a40e939SJosh Lehan if (!std::isfinite(maxValue)) 2182a40e939SJosh Lehan { 2192a40e939SJosh Lehan std::cerr << "MaxValue parameter not parsed for " 2202a40e939SJosh Lehan << interfacePath << "\n"; 2212a40e939SJosh Lehan continue; 2222a40e939SJosh Lehan } 2232a40e939SJosh Lehan 2247243217bSJosh Lehan double timeoutSecs = 0.0; 2252a40e939SJosh Lehan 2267243217bSJosh Lehan // Timeout is an optional numeric parameter 2277243217bSJosh Lehan auto timeoutFound = baseConfigMap.find("Timeout"); 2287243217bSJosh Lehan if (timeoutFound != baseConfigMap.end()) 2297243217bSJosh Lehan { 2307243217bSJosh Lehan timeoutSecs = std::visit(VariantToDoubleVisitor(), 2317243217bSJosh Lehan timeoutFound->second); 2327243217bSJosh Lehan } 2337243217bSJosh Lehan if (!(std::isfinite(timeoutSecs) && (timeoutSecs >= 0.0))) 2347243217bSJosh Lehan { 2357243217bSJosh Lehan std::cerr << "Timeout parameter not parsed for " 2367243217bSJosh Lehan << interfacePath << "\n"; 2377243217bSJosh Lehan continue; 2387243217bSJosh Lehan } 2397243217bSJosh Lehan 2407243217bSJosh Lehan std::string sensorName; 2417243217bSJosh Lehan std::string sensorUnits; 2427243217bSJosh Lehan 2437243217bSJosh Lehan // Name and Units are mandatory string parameters 2442a40e939SJosh Lehan auto nameFound = baseConfigMap.find("Name"); 2452a40e939SJosh Lehan if (nameFound == baseConfigMap.end()) 2462a40e939SJosh Lehan { 2472a40e939SJosh Lehan std::cerr << "Name parameter not found for " 2482a40e939SJosh Lehan << interfacePath << "\n"; 2492a40e939SJosh Lehan continue; 2502a40e939SJosh Lehan } 2512a40e939SJosh Lehan sensorName = 2522a40e939SJosh Lehan std::visit(VariantToStringVisitor(), nameFound->second); 2532a40e939SJosh Lehan if (sensorName.empty()) 2542a40e939SJosh Lehan { 2552a40e939SJosh Lehan std::cerr << "Name parameter not parsed for " 2562a40e939SJosh Lehan << interfacePath << "\n"; 2572a40e939SJosh Lehan continue; 2582a40e939SJosh Lehan } 2592a40e939SJosh Lehan 2607243217bSJosh Lehan auto unitsFound = baseConfigMap.find("Units"); 2617243217bSJosh Lehan if (unitsFound == baseConfigMap.end()) 2622a40e939SJosh Lehan { 2632a40e939SJosh Lehan std::cerr << "Units parameter not found for " 2642a40e939SJosh Lehan << interfacePath << "\n"; 2652a40e939SJosh Lehan continue; 2662a40e939SJosh Lehan } 2677243217bSJosh Lehan sensorUnits = 2687243217bSJosh Lehan std::visit(VariantToStringVisitor(), unitsFound->second); 2697243217bSJosh Lehan if (sensorUnits.empty()) 2702a40e939SJosh Lehan { 2717243217bSJosh Lehan std::cerr << "Units parameter not parsed for " 2722a40e939SJosh Lehan << interfacePath << "\n"; 2732a40e939SJosh Lehan continue; 2742a40e939SJosh Lehan } 2752a40e939SJosh Lehan 2762a40e939SJosh Lehan // on rescans, only update sensors we were signaled by 2772a40e939SJosh Lehan auto findSensor = sensors.find(sensorName); 2782a40e939SJosh Lehan if (!firstScan && (findSensor != sensors.end())) 2792a40e939SJosh Lehan { 2802a40e939SJosh Lehan std::string suffixName = "/"; 2812a40e939SJosh Lehan suffixName += findSensor->second->name; 2822a40e939SJosh Lehan bool found = false; 2832a40e939SJosh Lehan for (auto it = sensorsChanged->begin(); 2842a40e939SJosh Lehan it != sensorsChanged->end(); it++) 2852a40e939SJosh Lehan { 2862a40e939SJosh Lehan std::string suffixIt = "/"; 2872a40e939SJosh Lehan suffixIt += *it; 2882a40e939SJosh Lehan if (boost::ends_with(suffixIt, suffixName)) 2892a40e939SJosh Lehan { 2902a40e939SJosh Lehan sensorsChanged->erase(it); 2912a40e939SJosh Lehan findSensor->second = nullptr; 2922a40e939SJosh Lehan found = true; 2930362738dSJosh Lehan if constexpr (debug) 2940362738dSJosh Lehan { 2950362738dSJosh Lehan std::cerr << "ExternalSensor " << sensorName 2960362738dSJosh Lehan << " change found\n"; 2970362738dSJosh Lehan } 2982a40e939SJosh Lehan break; 2992a40e939SJosh Lehan } 3002a40e939SJosh Lehan } 3012a40e939SJosh Lehan if (!found) 3022a40e939SJosh Lehan { 3032a40e939SJosh Lehan continue; 3042a40e939SJosh Lehan } 3052a40e939SJosh Lehan } 3062a40e939SJosh Lehan 3072a40e939SJosh Lehan std::vector<thresholds::Threshold> sensorThresholds; 3082a40e939SJosh Lehan if (!parseThresholdsFromConfig(sensorData, sensorThresholds)) 3092a40e939SJosh Lehan { 3102a40e939SJosh Lehan std::cerr << "error populating thresholds for " 3112a40e939SJosh Lehan << sensorName << "\n"; 3122a40e939SJosh Lehan } 3132a40e939SJosh Lehan 3142a40e939SJosh Lehan auto findPowerOn = baseConfiguration.second.find("PowerState"); 3152a40e939SJosh Lehan PowerState readState = PowerState::always; 3162a40e939SJosh Lehan if (findPowerOn != baseConfiguration.second.end()) 3172a40e939SJosh Lehan { 3182a40e939SJosh Lehan std::string powerState = std::visit( 3192a40e939SJosh Lehan VariantToStringVisitor(), findPowerOn->second); 3202a40e939SJosh Lehan setReadState(powerState, readState); 3212a40e939SJosh Lehan } 3222a40e939SJosh Lehan 3232a40e939SJosh Lehan auto& sensorEntry = sensors[sensorName]; 3242a40e939SJosh Lehan sensorEntry = nullptr; 3252a40e939SJosh Lehan 3262a40e939SJosh Lehan sensorEntry = std::make_shared<ExternalSensor>( 3272a40e939SJosh Lehan sensorType, objectServer, dbusConnection, sensorName, 3287243217bSJosh Lehan sensorUnits, std::move(sensorThresholds), interfacePath, 3290362738dSJosh Lehan maxValue, minValue, timeoutSecs, readState); 3300362738dSJosh Lehan sensorEntry->initWriteHook( 3317243217bSJosh Lehan [&sensors, &reaperTimer]( 3327243217bSJosh Lehan const std::chrono::steady_clock::time_point& now) { 3337243217bSJosh Lehan updateReaper(sensors, reaperTimer, now); 3347243217bSJosh Lehan }); 3357243217bSJosh Lehan 3367243217bSJosh Lehan if constexpr (debug) 3377243217bSJosh Lehan { 3387243217bSJosh Lehan std::cerr << "ExternalSensor " << sensorName 3397243217bSJosh Lehan << " created\n"; 3407243217bSJosh Lehan } 3412a40e939SJosh Lehan } 3422a40e939SJosh Lehan }); 3432a40e939SJosh Lehan 3442a40e939SJosh Lehan getter->getConfiguration(std::vector<std::string>{sensorType}); 3452a40e939SJosh Lehan } 3462a40e939SJosh Lehan 3472a40e939SJosh Lehan int main() 3482a40e939SJosh Lehan { 3497243217bSJosh Lehan if constexpr (debug) 3507243217bSJosh Lehan { 3517243217bSJosh Lehan std::cerr << "ExternalSensor service starting up\n"; 3527243217bSJosh Lehan } 3537243217bSJosh Lehan 3542a40e939SJosh Lehan boost::asio::io_service io; 3552a40e939SJosh Lehan auto systemBus = std::make_shared<sdbusplus::asio::connection>(io); 3562a40e939SJosh Lehan systemBus->request_name("xyz.openbmc_project.ExternalSensor"); 3572a40e939SJosh Lehan sdbusplus::asio::object_server objectServer(systemBus); 3582a40e939SJosh Lehan boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>> 3592a40e939SJosh Lehan sensors; 3602a40e939SJosh Lehan std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches; 3612a40e939SJosh Lehan auto sensorsChanged = 3622a40e939SJosh Lehan std::make_shared<boost::container::flat_set<std::string>>(); 3637243217bSJosh Lehan boost::asio::steady_timer reaperTimer(io); 3642a40e939SJosh Lehan 365*8a17c303SEd Tanous io.post([&objectServer, &sensors, &systemBus, &reaperTimer]() { 366*8a17c303SEd Tanous createSensors(objectServer, sensors, systemBus, nullptr, reaperTimer); 3672a40e939SJosh Lehan }); 3682a40e939SJosh Lehan 3692a40e939SJosh Lehan boost::asio::deadline_timer filterTimer(io); 3702a40e939SJosh Lehan std::function<void(sdbusplus::message::message&)> eventHandler = 371*8a17c303SEd Tanous [&objectServer, &sensors, &systemBus, &sensorsChanged, &filterTimer, 372*8a17c303SEd Tanous &reaperTimer](sdbusplus::message::message& message) mutable { 3732a40e939SJosh Lehan if (message.is_method_error()) 3742a40e939SJosh Lehan { 3752a40e939SJosh Lehan std::cerr << "callback method error\n"; 3762a40e939SJosh Lehan return; 3772a40e939SJosh Lehan } 3780362738dSJosh Lehan 3790362738dSJosh Lehan auto messagePath = message.get_path(); 3800362738dSJosh Lehan sensorsChanged->insert(messagePath); 3810362738dSJosh Lehan if constexpr (debug) 3820362738dSJosh Lehan { 3830362738dSJosh Lehan std::cerr << "ExternalSensor change event received: " 3840362738dSJosh Lehan << messagePath << "\n"; 3850362738dSJosh Lehan } 3860362738dSJosh Lehan 3872a40e939SJosh Lehan // this implicitly cancels the timer 3882a40e939SJosh Lehan filterTimer.expires_from_now(boost::posix_time::seconds(1)); 3892a40e939SJosh Lehan 390*8a17c303SEd Tanous filterTimer.async_wait( 391*8a17c303SEd Tanous [&objectServer, &sensors, &systemBus, &sensorsChanged, 392*8a17c303SEd Tanous &reaperTimer](const boost::system::error_code& ec) mutable { 3930362738dSJosh Lehan if (ec != boost::system::errc::success) 3942a40e939SJosh Lehan { 3952a40e939SJosh Lehan if (ec != boost::asio::error::operation_aborted) 3962a40e939SJosh Lehan { 397*8a17c303SEd Tanous std::cerr << "callback error: " << ec.message() 398*8a17c303SEd Tanous << "\n"; 3992a40e939SJosh Lehan } 4002a40e939SJosh Lehan return; 4012a40e939SJosh Lehan } 4020362738dSJosh Lehan 403*8a17c303SEd Tanous createSensors(objectServer, sensors, systemBus, 4047243217bSJosh Lehan sensorsChanged, reaperTimer); 4052a40e939SJosh Lehan }); 4062a40e939SJosh Lehan }; 4072a40e939SJosh Lehan 4082a40e939SJosh Lehan auto match = std::make_unique<sdbusplus::bus::match::match>( 4092a40e939SJosh Lehan static_cast<sdbusplus::bus::bus&>(*systemBus), 4102a40e939SJosh Lehan "type='signal',member='PropertiesChanged',path_namespace='" + 4112a40e939SJosh Lehan std::string(inventoryPath) + "',arg0namespace='" + sensorType + "'", 4122a40e939SJosh Lehan eventHandler); 4132a40e939SJosh Lehan matches.emplace_back(std::move(match)); 4142a40e939SJosh Lehan 4157243217bSJosh Lehan if constexpr (debug) 4167243217bSJosh Lehan { 4177243217bSJosh Lehan std::cerr << "ExternalSensor service entering main loop\n"; 4187243217bSJosh Lehan } 4197243217bSJosh Lehan 4202a40e939SJosh Lehan io.run(); 4212a40e939SJosh Lehan } 422