Add chassis intrusion sensor daemon
Chassis intrusion signal is read from PCH via I2C or from GPIO.
Create a new daemon to poll its status and expose to dbus.
Related patches to run test:
- meta-phosphor: dbus-sensors: Enable new service of intrusion sensor
https://gerrit.openbmc-project.xyz/#/c/openbmc/meta-phosphor/+/17063/
- redfish: chassis: add property of physical security
https://gerrit.openbmc-project.xyz/#/c/openbmc/bmcweb/+/17691/
Tested-by: Check intrusion status value by redfish,
verified PCH solution on WFP and GPIO solution on STP.
Change-Id: Id5e67abbd80bbf2ef502db49fa183d92d0d31bda
Signed-off-by: Qiang XU <qiang.xu@linux.intel.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7201ac0..498fcca 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -33,6 +33,8 @@
set (IPMB_SRC_FILES src/Utils.cpp src/Thresholds.cpp)
+set (INTRUSION_SRC_FILES src/Utils.cpp src/ChassisIntrusionSensor.cpp)
+
set (EXTERNAL_PACKAGES Boost sdbusplus-project nlohmann-json)
set (SENSOR_LINK_LIBS -lsystemd stdc++fs sdbusplus)
@@ -133,6 +135,12 @@
add_dependencies (ipmbsensor sdbusplus)
target_link_libraries (ipmbsensor ${SENSOR_LINK_LIBS})
+add_executable (intrusionsensor src/IntrusionSensorMain.cpp
+ ${INTRUSION_SRC_FILES})
+add_dependencies (intrusionsensor sdbusplus-project)
+target_link_libraries (intrusionsensor ${SENSOR_LINK_LIBS})
+target_link_libraries (intrusionsensor i2c)
+
if (NOT YOCTO)
add_dependencies (fansensor ${EXTERNAL_PACKAGES})
add_dependencies (hwmontempsensor ${EXTERNAL_PACKAGES})
@@ -140,6 +148,7 @@
add_dependencies (cpusensor ${EXTERNAL_PACKAGES})
add_dependencies (exitairtempsensor ${EXTERNAL_PACKAGES})
add_dependencies (ipmbsensor ${EXTERNAL_PACKAGES})
+ add_dependencies (intrusionsensor ${EXTERNAL_PACKAGES})
endif ()
set (
@@ -150,8 +159,9 @@
${PROJECT_SOURCE_DIR}/service_files/xyz.openbmc_project.hwmontempsensor.service
${PROJECT_SOURCE_DIR}/service_files/xyz.openbmc_project.exitairsensor.service
${PROJECT_SOURCE_DIR}/service_files/xyz.openbmc_project.ipmbsensor.service
+ ${PROJECT_SOURCE_DIR}/service_files/xyz.openbmc_project.intrusionsensor.service
)
install (TARGETS fansensor hwmontempsensor cpusensor adcsensor ipmbsensor
- exitairtempsensor DESTINATION sbin)
+ exitairtempsensor intrusionsensor DESTINATION sbin)
install (FILES ${SERVICE_FILES} DESTINATION /lib/systemd/system/)
diff --git a/include/ChassisIntrusionSensor.hpp b/include/ChassisIntrusionSensor.hpp
new file mode 100644
index 0000000..fe04fc9
--- /dev/null
+++ b/include/ChassisIntrusionSensor.hpp
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <sdbusplus/asio/object_server.hpp>
+
+enum IntrusionSensorType
+{
+ pch,
+ gpio
+};
+
+class ChassisIntrusionSensor
+{
+ public:
+ ChassisIntrusionSensor(
+ boost::asio::io_service& io,
+ std::shared_ptr<sdbusplus::asio::dbus_interface> iface);
+
+ ~ChassisIntrusionSensor();
+
+ void start(IntrusionSensorType type, int busId, int slaveAddr,
+ int gpioIndex, bool gpioInverted);
+
+ private:
+ std::shared_ptr<sdbusplus::asio::dbus_interface> mIface;
+ std::shared_ptr<sdbusplus::asio::connection> mDbusConn;
+
+ IntrusionSensorType mType;
+
+ // intrusion status. 0: not intruded, 1: intruded
+ std::string mValue = "unknown";
+ std::string mOldValue = "unknown";
+
+ // valid if it is PCH register via i2c
+ int mBusId;
+ int mSlaveAddr;
+ boost::asio::deadline_timer mPollTimer;
+
+ // valid if it is via GPIO
+ int mGpioIndex;
+ bool mGpioInverted;
+ boost::asio::ip::tcp::socket mInputDev;
+ int mFd;
+
+ // common members
+ std::string mOverriddenValue = "unknown";
+ bool mOverridenState = false;
+ bool mInternalSet = false;
+
+ bool mInitialized = false;
+
+ void updateValue(const std::string newValue);
+ int i2cReadFromPch(int busId, int slaveAddr);
+ void pollSensorStatusByPch();
+ void readGpio();
+ void pollSensorStatusByGpio();
+ void initGpioDeviceFile(const int index);
+ int setSensorValue(const std::string& req, std::string& propertyValue);
+};
diff --git a/service_files/xyz.openbmc_project.intrusionsensor.service b/service_files/xyz.openbmc_project.intrusionsensor.service
new file mode 100644
index 0000000..5b407e4
--- /dev/null
+++ b/service_files/xyz.openbmc_project.intrusionsensor.service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Intrusion Sensor
+StopWhenUnneeded=false
+
+[Service]
+Restart=always
+RestartSec=5
+ExecStart=/usr/bin/env intrusionsensor
+SyslogIdentifier=intrusion-sensor
+
+[Install]
+WantedBy=multi-user.target
diff --git a/src/ChassisIntrusionSensor.cpp b/src/ChassisIntrusionSensor.cpp
new file mode 100644
index 0000000..22c03ab
--- /dev/null
+++ b/src/ChassisIntrusionSensor.cpp
@@ -0,0 +1,373 @@
+/*
+// Copyright (c) 2018 Intel 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 <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+#include <ChassisIntrusionSensor.hpp>
+#include <boost/asio.hpp>
+#include <boost/bind.hpp>
+#include <chrono>
+#include <iostream>
+#include <sdbusplus/asio/object_server.hpp>
+#include <string>
+#include <thread>
+
+extern "C" {
+#include <i2c/smbus.h>
+#include <linux/i2c-dev.h>
+}
+
+static constexpr bool DEBUG = false;
+
+static constexpr unsigned int intrusionSensorPollSec = 1;
+
+// SMLink Status Register
+const static constexpr size_t pchStatusRegIntrusion = 0x04;
+
+// Status bit field masks
+const static constexpr size_t pchRegMaskIntrusion = 0x01;
+
+// Hold current PCH register values
+static unsigned int intrudeValue;
+
+// gpio sysfs path
+constexpr const char* gpioPath = "/sys/class/gpio/";
+
+void ChassisIntrusionSensor::updateValue(const std::string newValue)
+{
+ // indicate that it is internal set call
+ mInternalSet = true;
+ mIface->set_property("Status", newValue);
+
+ mValue = newValue;
+
+ if (mOldValue == "Normal" && mValue != "Normal")
+ {
+ std::cerr << "save to SEL for intrusion assert event \n";
+ // TODO: call add SEL log API, depends on patch #13956
+ mOldValue = mValue;
+ }
+ else if (mOldValue != "Normal" && mValue == "Normal")
+ {
+ std::cerr << "save to SEL for intrusion de-assert event \n";
+ // TODO: call add SEL log API, depends on patch #13956
+ mOldValue = mValue;
+ }
+}
+
+int ChassisIntrusionSensor::i2cReadFromPch(int busId, int slaveAddr)
+{
+ std::string i2cBus = "/dev/i2c-" + std::to_string(busId);
+
+ int fd = open(i2cBus.c_str(), O_RDWR | O_CLOEXEC);
+ if (fd < 0)
+ {
+ std::cerr << "unable to open i2c device \n";
+ return -1;
+ }
+ if (ioctl(fd, I2C_SLAVE_FORCE, slaveAddr) < 0)
+ {
+ std::cerr << "unable to set device address\n";
+ close(fd);
+ return -1;
+ }
+
+ unsigned long funcs = 0;
+ if (ioctl(fd, I2C_FUNCS, &funcs) < 0)
+ {
+ std::cerr << "not support I2C_FUNCS \n";
+ close(fd);
+ return -1;
+ }
+
+ if (!(funcs & I2C_FUNC_SMBUS_READ_BYTE_DATA))
+ {
+ std::cerr << "not support I2C_FUNC_SMBUS_READ_BYTE_DATA \n";
+ close(fd);
+ return -1;
+ }
+
+ unsigned int statusValue;
+ unsigned int statusMask = pchRegMaskIntrusion;
+ unsigned int statusReg = pchStatusRegIntrusion;
+
+ statusValue = i2c_smbus_read_byte_data(fd, statusReg);
+ if (DEBUG)
+ {
+ std::cout << "\nRead bus " << busId << " addr " << slaveAddr
+ << ", value = " << statusValue << "\n";
+ }
+
+ close(fd);
+
+ if (statusValue < 0)
+ {
+ std::cerr << "i2c_smbus_read_byte_data failed \n";
+ return -1;
+ }
+
+ // Get status value with mask
+ int newValue = statusValue & statusMask;
+
+ if (DEBUG)
+ {
+ std::cout << "statusValue is " << statusValue << "\n";
+ std::cout << "Intrusion sensor value is " << newValue << "\n";
+ }
+
+ return newValue;
+}
+
+void ChassisIntrusionSensor::pollSensorStatusByPch()
+{
+ // setting a new experation implicitly cancels any pending async wait
+ mPollTimer.expires_from_now(
+ boost::posix_time::seconds(intrusionSensorPollSec));
+
+ mPollTimer.async_wait([&](const boost::system::error_code& ec) {
+ // case of timer expired
+ if (!ec)
+ {
+ int statusValue = i2cReadFromPch(mBusId, mSlaveAddr);
+ std::string newValue = statusValue ? "HardwareIntrusion" : "Normal";
+
+ // save value
+ if (mOverridenState)
+ {
+ newValue = mOverriddenValue;
+ }
+
+ if (newValue != "unknown" && mValue != newValue)
+ {
+ std::cout << "update value from " << mValue << " to "
+ << newValue << "\n";
+ updateValue(newValue);
+ }
+
+ // trigger next polling
+ pollSensorStatusByPch();
+ }
+ // case of being canceled
+ else if (ec == boost::asio::error::operation_aborted)
+ {
+ std::cerr << "Timer of intrusion sensor is cancelled. Return \n";
+ return;
+ }
+ });
+}
+
+void ChassisIntrusionSensor::readGpio()
+{
+ constexpr size_t readSize = sizeof("0");
+ std::string readBuf;
+ readBuf.resize(readSize);
+ lseek(mFd, 0, SEEK_SET);
+ size_t r = ::read(mFd, readBuf.data(), readSize);
+ if (r != readSize)
+ {
+ std::cerr << "Error reading gpio\n";
+ }
+ else
+ {
+ bool value = std::stoi(readBuf);
+ if (mGpioInverted)
+ {
+ value = !value;
+ }
+
+ // set string defined in chassis redfish schema
+ std::string newValue = value ? "HardwareIntrusion" : "Normal";
+
+ if (DEBUG)
+ {
+ std::cout << "\nGPIO value is " << value << "\n";
+ std::cout << "Intrusion sensor value is " << newValue << "\n";
+ }
+
+ // save value
+ if (mOverridenState)
+ {
+ newValue = mOverriddenValue;
+ }
+
+ if (newValue != "unknown" && mValue != newValue)
+ {
+ std::cout << "update value from " << mValue << " to " << newValue
+ << "\n";
+ updateValue(newValue);
+ }
+ }
+}
+
+void ChassisIntrusionSensor::pollSensorStatusByGpio(void)
+{
+ mInputDev.async_wait(
+ boost::asio::ip::tcp::socket::wait_error,
+ [this](const boost::system::error_code& ec) {
+ if (ec == boost::system::errc::bad_file_descriptor)
+ {
+ return; // we're being destroyed
+ }
+ else if (ec)
+ {
+ std::cerr << "Error on GPIO based intrusion sensor socket\n";
+ }
+ else
+ {
+ readGpio();
+ }
+ pollSensorStatusByGpio();
+ });
+}
+
+void ChassisIntrusionSensor::initGpioDeviceFile(const int index)
+{
+ std::string device = gpioPath + std::string("gpio") + std::to_string(index);
+ mFd = open((device + "/value").c_str(), O_RDONLY);
+ if (mFd < 0)
+ {
+ std::cerr << "Error opening gpio " << index << "\n";
+ return;
+ }
+ mInputDev.assign(boost::asio::ip::tcp::v4(), mFd);
+}
+
+int ChassisIntrusionSensor::setSensorValue(const std::string& req,
+ std::string& propertyValue)
+{
+ if (mInternalSet)
+ {
+ mInternalSet = false;
+ propertyValue = req;
+ }
+ else
+ {
+ mOverriddenValue = req;
+ mOverridenState = true;
+ }
+ return 1;
+}
+
+void ChassisIntrusionSensor::start(IntrusionSensorType type, int busId,
+ int slaveAddr, int gpioIndex,
+ bool gpioInverted)
+{
+ if (DEBUG)
+ {
+ std::cerr << "enter ChassisIntrusionSensor::start, type = " << type
+ << "\n";
+ if (type == IntrusionSensorType::pch)
+ {
+ std::cerr << "busId = " << busId << ", slaveAddr = " << slaveAddr
+ << "\n";
+ }
+ else if (type == IntrusionSensorType::gpio)
+ {
+ std::cerr << "gpioIndex = " << gpioIndex
+ << ", gpioInverted = " << gpioInverted << "\n";
+ }
+ }
+
+ if ((type == IntrusionSensorType::pch && busId == mBusId &&
+ slaveAddr == mSlaveAddr) ||
+ (type == IntrusionSensorType::gpio && gpioIndex == mGpioIndex &&
+ gpioInverted == mGpioInverted))
+ {
+ return;
+ }
+
+ mType = type;
+ mBusId = busId;
+ mSlaveAddr = slaveAddr;
+ mGpioIndex = gpioIndex;
+ mGpioInverted = gpioInverted;
+
+ if ((mType == IntrusionSensorType::pch && mBusId > 0 && mSlaveAddr > 0) ||
+ (mType == IntrusionSensorType::gpio && mGpioIndex > 0))
+ {
+ // initialize first if not initialized before
+ if (!mInitialized)
+ {
+ mIface->register_property(
+ "Status", mValue,
+ [&](const std::string& req, std::string& propertyValue) {
+ return setSensorValue(req, propertyValue);
+ });
+ mIface->initialize();
+
+ if (mType == IntrusionSensorType::gpio)
+ {
+ initGpioDeviceFile(mGpioIndex);
+ }
+
+ mInitialized = true;
+ }
+
+ // start polling value
+ if (mType == IntrusionSensorType::pch)
+ {
+ pollSensorStatusByPch();
+ }
+ else if (mType == IntrusionSensorType::gpio && mFd > 0)
+ {
+ pollSensorStatusByGpio();
+ }
+ }
+
+ // invalid para, release resource
+ else
+ {
+ if (mInitialized)
+ {
+ if (mType == IntrusionSensorType::pch)
+ {
+ mPollTimer.cancel();
+ }
+ else if (mType == IntrusionSensorType::gpio)
+ {
+ mInputDev.close();
+ close(mFd);
+ }
+ mInitialized = false;
+ }
+ }
+}
+
+ChassisIntrusionSensor::ChassisIntrusionSensor(
+ boost::asio::io_service& io,
+ std::shared_ptr<sdbusplus::asio::dbus_interface> iface) :
+ mPollTimer(io),
+ mIface(iface), mInputDev(io), mType(IntrusionSensorType::gpio), mBusId(-1),
+ mSlaveAddr(-1), mGpioIndex(-1), mGpioInverted(false), mValue("unknown"),
+ mOldValue("unknown")
+{
+}
+
+ChassisIntrusionSensor::~ChassisIntrusionSensor()
+{
+ if (mType == IntrusionSensorType::pch)
+ {
+ mPollTimer.cancel();
+ }
+ else if (mType == IntrusionSensorType::gpio)
+ {
+ mInputDev.close();
+ close(mFd);
+ }
+}
diff --git a/src/IntrusionSensorMain.cpp b/src/IntrusionSensorMain.cpp
new file mode 100644
index 0000000..2def3c1
--- /dev/null
+++ b/src/IntrusionSensorMain.cpp
@@ -0,0 +1,231 @@
+/*
+// Copyright (c) 2018 Intel 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 <ChassisIntrusionSensor.hpp>
+#include <Utils.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/asio.hpp>
+#include <chrono>
+#include <ctime>
+#include <iostream>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+#include <sdbusplus/asio/sd_event.hpp>
+#include <sdbusplus/bus.hpp>
+#include <sdbusplus/exception.hpp>
+#include <sdbusplus/server.hpp>
+#include <sdbusplus/timer.hpp>
+
+static constexpr bool DEBUG = false;
+
+static constexpr const char* sensorType =
+ "xyz.openbmc_project.Configuration.ChassisIntrusionSensor";
+
+static bool getIntrusionSensorConfig(
+ std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+ IntrusionSensorType* pType, int* pBusId, int* pSlaveAddr, int* pGpioIndex,
+ bool* pGpioInverted)
+{
+ // find matched configuration according to sensor type
+ ManagedObjectType sensorConfigurations;
+ bool useCache = false;
+
+ if (!getSensorConfiguration(sensorType, dbusConnection,
+ sensorConfigurations, useCache))
+ {
+ std::cerr << "error communicating to entity manager\n";
+ return false;
+ }
+
+ const SensorData* sensorData = nullptr;
+ const std::pair<std::string,
+ boost::container::flat_map<std::string, BasicVariantType>>*
+ baseConfiguration = nullptr;
+
+ // Get bus and addr of matched configuration
+ for (const std::pair<sdbusplus::message::object_path, SensorData>& sensor :
+ sensorConfigurations)
+ {
+ baseConfiguration = nullptr;
+ sensorData = &(sensor.second);
+
+ // match sensor type
+ auto sensorBase = sensorData->find(sensorType);
+ if (sensorBase == sensorData->end())
+ {
+ std::cerr << "error finding base configuration \n";
+ continue;
+ }
+
+ baseConfiguration = &(*sensorBase);
+
+ // judge class, "Gpio" or "I2C"
+ auto findClass = baseConfiguration->second.find("Class");
+ if (findClass != baseConfiguration->second.end() &&
+ sdbusplus::message::variant_ns::get<std::string>(
+ findClass->second) == "Gpio")
+ {
+ *pType = IntrusionSensorType::gpio;
+ }
+ else
+ {
+ *pType = IntrusionSensorType::pch;
+ }
+
+ // case to find GPIO info
+ if (*pType == IntrusionSensorType::gpio)
+ {
+ auto gpioConfig =
+ sensorData->find(sensorType + std::string(".GpioIntrusion"));
+
+ if (gpioConfig == sensorData->end())
+ {
+ std::cerr
+ << "error finding GpioIntrusion info in configuration \n";
+ continue;
+ }
+
+ auto findGpioIndex = gpioConfig->second.find("Index");
+ auto findGpioPolarity = gpioConfig->second.find("Polarity");
+
+ if (findGpioIndex == gpioConfig->second.end() ||
+ findGpioPolarity == gpioConfig->second.end())
+ {
+ std::cerr << "error finding gpio info in configuration \n";
+ continue;
+ }
+
+ try
+ {
+ *pGpioIndex = sdbusplus::message::variant_ns::get<uint64_t>(
+ findGpioIndex->second);
+ *pGpioInverted =
+ (sdbusplus::message::variant_ns::get<std::string>(
+ findGpioPolarity->second) == "Low");
+ }
+ catch (const std::bad_variant_access& e)
+ {
+ std::cerr << "invalid value for gpio info in config. \n";
+ continue;
+ }
+
+ if (DEBUG)
+ {
+ std::cout << "find matched GPIO index " << *pGpioIndex
+ << ", polarity inverted flag is " << *pGpioInverted
+ << "\n";
+ }
+
+ return true;
+ }
+
+ // case to find I2C info
+ else if (*pType == IntrusionSensorType::pch)
+ {
+ auto findBus = baseConfiguration->second.find("Bus");
+ auto findAddress = baseConfiguration->second.find("Address");
+ if (findBus == baseConfiguration->second.end() ||
+ findAddress == baseConfiguration->second.end())
+ {
+ std::cerr << "error finding bus or address in configuration \n";
+ continue;
+ }
+
+ try
+ {
+ *pBusId = sdbusplus::message::variant_ns::get<uint64_t>(
+ findBus->second);
+ *pSlaveAddr = sdbusplus::message::variant_ns::get<uint64_t>(
+ findAddress->second);
+ }
+ catch (const std::bad_variant_access& e)
+ {
+ std::cerr << "invalid value for bus or address in config. \n";
+ continue;
+ }
+
+ if (DEBUG)
+ {
+ std::cout << "find matched bus " << *pBusId
+ << ", matched slave addr " << *pSlaveAddr << "\n";
+ }
+ return true;
+ }
+ }
+
+ std::cerr << "can't find matched I2C or GPIO configuration. \n";
+ *pBusId = -1;
+ *pSlaveAddr = -1;
+ *pGpioIndex = -1;
+ return false;
+}
+
+int main()
+{
+ int busId = -1, slaveAddr = -1, gpioIndex = -1;
+ bool gpioInverted = false;
+ IntrusionSensorType type = IntrusionSensorType::gpio;
+
+ // setup connection to dbus
+ boost::asio::io_service io;
+ auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+ auto objServer = sdbusplus::asio::object_server(systemBus);
+
+ // setup object server, define interface
+ systemBus->request_name("xyz.openbmc_project.IntrusionSensor");
+
+ std::shared_ptr<sdbusplus::asio::dbus_interface> ifaceChassis =
+ objServer.add_interface(
+ "/xyz/openbmc_project/Intrusion/Chassis_Intrusion",
+ "xyz.openbmc_project.Chassis.Intrusion");
+
+ ChassisIntrusionSensor chassisIntrusionSensor(io, ifaceChassis);
+
+ if (getIntrusionSensorConfig(systemBus, &type, &busId, &slaveAddr,
+ &gpioIndex, &gpioInverted))
+ {
+ chassisIntrusionSensor.start(type, busId, slaveAddr, gpioIndex,
+ gpioInverted);
+ }
+
+ // callback to handle configuration change
+ std::function<void(sdbusplus::message::message&)> eventHandler =
+ [&](sdbusplus::message::message& message) {
+ if (message.is_method_error())
+ {
+ std::cerr << "callback method error\n";
+ return;
+ }
+
+ std::cout << "rescan due to configuration change \n";
+ if (getIntrusionSensorConfig(systemBus, &type, &busId, &slaveAddr,
+ &gpioIndex, &gpioInverted))
+ {
+ chassisIntrusionSensor.start(type, busId, slaveAddr, gpioIndex,
+ gpioInverted);
+ }
+ };
+
+ 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);
+
+ io.run();
+
+ return 0;
+}