Move source files into application-specific sub-directories

Currently, dbus-sensors implement multiple applications:
 - psusensor
 - adcsensor
 - intelcpusensor
 - hwmontempsensor
 - ipmbsensor
 - nvmesensor
 - externalsensor
 - mcutempsensor
 - intrusionsensor
 - fansensor
 - exitairtempsensor

This commit is to create separate directories for each application so
that things can be separated more easily and the files are smaller,
instead of creating one huge file for the sensor implementation.

There was some discussion in discord on this. [1][2]

[1]: https://discord.com/channels/775381525260664832/1187158775438778408/1284106093756289067
[2]: https://discord.com/channels/775381525260664832/867820390406422538/1303217796821553214

Signed-off-by: George Liu <liuxiwei@ieisystem.com>
Change-Id: I258fc2ee7d8f939c7b83a07350395e78775b2b8d
diff --git a/src/hwmon-temp/HwmonTempMain.cpp b/src/hwmon-temp/HwmonTempMain.cpp
new file mode 100644
index 0000000..f7ccb4f
--- /dev/null
+++ b/src/hwmon-temp/HwmonTempMain.cpp
@@ -0,0 +1,667 @@
+/*
+// Copyright (c) 2017 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 "DeviceMgmt.hpp"
+#include "HwmonTempSensor.hpp"
+#include "SensorPaths.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/post.hpp>
+#include <boost/asio/steady_timer.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.hpp>
+#include <sdbusplus/bus/match.hpp>
+#include <sdbusplus/message.hpp>
+#include <sdbusplus/message/native_types.hpp>
+
+#include <algorithm>
+#include <array>
+#include <chrono>
+#include <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <functional>
+#include <ios>
+#include <iostream>
+#include <memory>
+#include <optional>
+#include <regex>
+#include <string>
+#include <system_error>
+#include <utility>
+#include <variant>
+#include <vector>
+
+static constexpr float pollRateDefault = 0.5;
+
+static constexpr double maxValuePressure = 120000;      // Pascals
+static constexpr double minValuePressure = 30000;       // Pascals
+
+static constexpr double maxValueRelativeHumidity = 100; // PercentRH
+static constexpr double minValueRelativeHumidity = 0;   // PercentRH
+
+static constexpr double maxValueTemperature = 127;      // DegreesC
+static constexpr double minValueTemperature = -128;     // DegreesC
+
+namespace fs = std::filesystem;
+
+static const I2CDeviceTypeMap sensorTypes{
+    {"ADM1021", I2CDeviceType{"adm1021", true}},
+    {"DPS310", I2CDeviceType{"dps310", false}},
+    {"EMC1403", I2CDeviceType{"emc1403", true}},
+    {"EMC1412", I2CDeviceType{"emc1412", true}},
+    {"EMC1413", I2CDeviceType{"emc1413", true}},
+    {"EMC1414", I2CDeviceType{"emc1414", true}},
+    {"HDC1080", I2CDeviceType{"hdc1080", false}},
+    {"JC42", I2CDeviceType{"jc42", true}},
+    {"LM75A", I2CDeviceType{"lm75a", true}},
+    {"LM95234", I2CDeviceType{"lm95234", true}},
+    {"MAX31725", I2CDeviceType{"max31725", true}},
+    {"MAX31730", I2CDeviceType{"max31730", true}},
+    {"MAX6581", I2CDeviceType{"max6581", true}},
+    {"MAX6654", I2CDeviceType{"max6654", true}},
+    {"MAX6639", I2CDeviceType{"max6639", true}},
+    {"MCP9600", I2CDeviceType{"mcp9600", false}},
+    {"NCT6779", I2CDeviceType{"nct6779", true}},
+    {"NCT7802", I2CDeviceType{"nct7802", true}},
+    {"PT5161L", I2CDeviceType{"pt5161l", true}},
+    {"SBTSI", I2CDeviceType{"sbtsi", true}},
+    {"SI7020", I2CDeviceType{"si7020", false}},
+    {"TMP100", I2CDeviceType{"tmp100", true}},
+    {"TMP112", I2CDeviceType{"tmp112", true}},
+    {"TMP175", I2CDeviceType{"tmp175", true}},
+    {"TMP421", I2CDeviceType{"tmp421", true}},
+    {"TMP432", I2CDeviceType{"tmp432", true}},
+    {"TMP441", I2CDeviceType{"tmp441", true}},
+    {"TMP461", I2CDeviceType{"tmp461", true}},
+    {"TMP464", I2CDeviceType{"tmp464", true}},
+    {"TMP468", I2CDeviceType{"tmp468", true}},
+    {"TMP75", I2CDeviceType{"tmp75", true}},
+    {"W83773G", I2CDeviceType{"w83773g", true}},
+};
+
+static struct SensorParams
+    getSensorParameters(const std::filesystem::path& path)
+{
+    // offset is to default to 0 and scale to 1, see lore
+    // https://lore.kernel.org/linux-iio/5c79425f-6e88-36b6-cdfe-4080738d039f@metafoo.de/
+    struct SensorParams tmpSensorParameters = {
+        .minValue = minValueTemperature,
+        .maxValue = maxValueTemperature,
+        .offsetValue = 0.0,
+        .scaleValue = 1.0,
+        .units = sensor_paths::unitDegreesC,
+        .typeName = "temperature"};
+
+    // For IIO RAW sensors we get a raw_value, an offset, and scale
+    // to compute the value = (raw_value + offset) * scale
+    // with a _raw IIO device we need to get the
+    // offsetValue and scaleValue from the driver
+    // these are used to compute the reading in
+    // units that have yet to be scaled for D-Bus.
+    const std::string pathStr = path.string();
+    if (pathStr.ends_with("_raw"))
+    {
+        std::string pathOffsetStr =
+            pathStr.substr(0, pathStr.size() - 4) + "_offset";
+        std::optional<double> tmpOffsetValue = readFile(pathOffsetStr, 1.0);
+        // In case there is nothing to read skip this device
+        // This is not an error condition see lore
+        // https://lore.kernel.org/linux-iio/5c79425f-6e88-36b6-cdfe-4080738d039f@metafoo.de/
+        if (tmpOffsetValue)
+        {
+            tmpSensorParameters.offsetValue = *tmpOffsetValue;
+        }
+
+        std::string pathScaleStr =
+            pathStr.substr(0, pathStr.size() - 4) + "_scale";
+        std::optional<double> tmpScaleValue = readFile(pathScaleStr, 1.0);
+        // In case there is nothing to read skip this device
+        // This is not an error condition see lore
+        // https://lore.kernel.org/linux-iio/5c79425f-6e88-36b6-cdfe-4080738d039f@metafoo.de/
+        if (tmpScaleValue)
+        {
+            tmpSensorParameters.scaleValue = *tmpScaleValue;
+        }
+    }
+
+    // Temperatures are read in milli degrees Celsius, we need
+    // degrees Celsius. Pressures are read in kilopascal, we need
+    // Pascals.  On D-Bus for Open BMC we use the International
+    // System of Units without prefixes. Links to the kernel
+    // documentation:
+    // https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
+    // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-iio
+    if (path.filename() == "in_pressure_input" ||
+        path.filename() == "in_pressure_raw")
+    {
+        tmpSensorParameters.minValue = minValuePressure;
+        tmpSensorParameters.maxValue = maxValuePressure;
+        // Pressures are read in kilopascal, we need Pascals.
+        tmpSensorParameters.scaleValue *= 1000.0;
+        tmpSensorParameters.typeName = "pressure";
+        tmpSensorParameters.units = sensor_paths::unitPascals;
+    }
+    else if (path.filename() == "in_humidityrelative_input" ||
+             path.filename() == "in_humidityrelative_raw")
+    {
+        tmpSensorParameters.minValue = minValueRelativeHumidity;
+        tmpSensorParameters.maxValue = maxValueRelativeHumidity;
+        // Relative Humidity are read in milli-percent, we need percent.
+        tmpSensorParameters.scaleValue *= 0.001;
+        tmpSensorParameters.typeName = "humidity";
+        tmpSensorParameters.units = sensor_paths::unitPercentRH;
+    }
+    else
+    {
+        // Temperatures are read in milli degrees Celsius,
+        // we need degrees Celsius.
+        tmpSensorParameters.scaleValue *= 0.001;
+    }
+
+    return tmpSensorParameters;
+}
+
+struct SensorConfigKey
+{
+    uint64_t bus;
+    uint64_t addr;
+    bool operator<(const SensorConfigKey& other) const
+    {
+        if (bus != other.bus)
+        {
+            return bus < other.bus;
+        }
+        return addr < other.addr;
+    }
+};
+
+struct SensorConfig
+{
+    std::string sensorPath;
+    SensorData sensorData;
+    std::string interface;
+    SensorBaseConfigMap config;
+    std::vector<std::string> name;
+};
+
+using SensorConfigMap =
+    boost::container::flat_map<SensorConfigKey, SensorConfig>;
+
+static SensorConfigMap
+    buildSensorConfigMap(const ManagedObjectType& sensorConfigs)
+{
+    SensorConfigMap configMap;
+    for (const auto& [path, cfgData] : sensorConfigs)
+    {
+        for (const auto& [intf, cfg] : cfgData)
+        {
+            auto busCfg = cfg.find("Bus");
+            auto addrCfg = cfg.find("Address");
+            if ((busCfg == cfg.end()) || (addrCfg == cfg.end()))
+            {
+                continue;
+            }
+
+            if ((std::get_if<uint64_t>(&busCfg->second) == nullptr) ||
+                (std::get_if<uint64_t>(&addrCfg->second) == nullptr))
+            {
+                std::cerr << path.str << " Bus or Address invalid\n";
+                continue;
+            }
+
+            std::vector<std::string> hwmonNames;
+            auto nameCfg = cfg.find("Name");
+            if (nameCfg != cfg.end())
+            {
+                hwmonNames.push_back(std::get<std::string>(nameCfg->second));
+                size_t i = 1;
+                while (true)
+                {
+                    auto sensorNameCfg = cfg.find("Name" + std::to_string(i));
+                    if (sensorNameCfg == cfg.end())
+                    {
+                        break;
+                    }
+                    hwmonNames.push_back(
+                        std::get<std::string>(sensorNameCfg->second));
+                    i++;
+                }
+            }
+
+            SensorConfigKey key = {std::get<uint64_t>(busCfg->second),
+                                   std::get<uint64_t>(addrCfg->second)};
+            SensorConfig val = {path.str, cfgData, intf, cfg, hwmonNames};
+
+            auto [it, inserted] = configMap.emplace(key, std::move(val));
+            if (!inserted)
+            {
+                std::cerr << path.str << ": ignoring duplicate entry for {"
+                          << key.bus << ", 0x" << std::hex << key.addr
+                          << std::dec << "}\n";
+            }
+        }
+    }
+    return configMap;
+}
+
+void createSensors(
+    boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
+    boost::container::flat_map<std::string, std::shared_ptr<HwmonTempSensor>>&
+        sensors,
+    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
+    const std::shared_ptr<boost::container::flat_set<std::string>>&
+        sensorsChanged,
+    bool activateOnly)
+{
+    auto getter = std::make_shared<GetSensorConfiguration>(
+        dbusConnection,
+        [&io, &objectServer, &sensors, &dbusConnection, sensorsChanged,
+         activateOnly](const ManagedObjectType& sensorConfigurations) {
+            bool firstScan = sensorsChanged == nullptr;
+
+            SensorConfigMap configMap =
+                buildSensorConfigMap(sensorConfigurations);
+
+            auto devices =
+                instantiateDevices(sensorConfigurations, sensors, sensorTypes);
+
+            // IIO _raw devices look like this on sysfs:
+            //     /sys/bus/iio/devices/iio:device0/in_temp_raw
+            //     /sys/bus/iio/devices/iio:device0/in_temp_offset
+            //     /sys/bus/iio/devices/iio:device0/in_temp_scale
+            //
+            // Other IIO devices look like this on sysfs:
+            //     /sys/bus/iio/devices/iio:device1/in_temp_input
+            //     /sys/bus/iio/devices/iio:device1/in_pressure_input
+            std::vector<fs::path> paths;
+            fs::path root("/sys/bus/iio/devices");
+            findFiles(root, R"(in_temp\d*_(input|raw))", paths);
+            findFiles(root, R"(in_pressure\d*_(input|raw))", paths);
+            findFiles(root, R"(in_humidityrelative\d*_(input|raw))", paths);
+            findFiles(fs::path("/sys/class/hwmon"), R"(temp\d+_input)", paths);
+
+            // iterate through all found temp and pressure sensors,
+            // and try to match them with configuration
+            for (auto& path : paths)
+            {
+                std::smatch match;
+                const std::string pathStr = path.string();
+                auto directory = path.parent_path();
+                fs::path device;
+
+                std::string deviceName;
+                std::error_code ec;
+                if (pathStr.starts_with("/sys/bus/iio/devices"))
+                {
+                    device = fs::canonical(directory, ec);
+                    if (ec)
+                    {
+                        std::cerr << "Fail to find device in path [" << pathStr
+                                  << "]\n";
+                        continue;
+                    }
+                    deviceName = device.parent_path().stem();
+                }
+                else
+                {
+                    device = fs::canonical(directory / "device", ec);
+                    if (ec)
+                    {
+                        std::cerr << "Fail to find device in path [" << pathStr
+                                  << "]\n";
+                        continue;
+                    }
+                    deviceName = device.stem();
+                }
+
+                uint64_t bus = 0;
+                uint64_t addr = 0;
+                if (!getDeviceBusAddr(deviceName, bus, addr))
+                {
+                    continue;
+                }
+
+                auto thisSensorParameters = getSensorParameters(path);
+                auto findSensorCfg = configMap.find({bus, addr});
+                if (findSensorCfg == configMap.end())
+                {
+                    continue;
+                }
+
+                const std::string& interfacePath =
+                    findSensorCfg->second.sensorPath;
+                auto findI2CDev = devices.find(interfacePath);
+
+                std::shared_ptr<I2CDevice> i2cDev;
+                if (findI2CDev != devices.end())
+                {
+                    // If we're only looking to activate newly-instantiated i2c
+                    // devices and this sensor's underlying device was already
+                    // there before this call, there's nothing more to do here.
+                    if (activateOnly && !findI2CDev->second.second)
+                    {
+                        continue;
+                    }
+                    i2cDev = findI2CDev->second.first;
+                }
+
+                const SensorData& sensorData = findSensorCfg->second.sensorData;
+                std::string sensorType = findSensorCfg->second.interface;
+                auto pos = sensorType.find_last_of('.');
+                if (pos != std::string::npos)
+                {
+                    sensorType = sensorType.substr(pos + 1);
+                }
+                const SensorBaseConfigMap& baseConfigMap =
+                    findSensorCfg->second.config;
+                std::vector<std::string>& hwmonName =
+                    findSensorCfg->second.name;
+
+                // Temperature has "Name", pressure has "Name1"
+                auto findSensorName = baseConfigMap.find("Name");
+                int index = 1;
+                if (thisSensorParameters.typeName == "pressure" ||
+                    thisSensorParameters.typeName == "humidity")
+                {
+                    findSensorName = baseConfigMap.find("Name1");
+                    index = 2;
+                }
+
+                if (findSensorName == baseConfigMap.end())
+                {
+                    std::cerr << "could not determine configuration name for "
+                              << deviceName << "\n";
+                    continue;
+                }
+                std::string sensorName =
+                    std::get<std::string>(findSensorName->second);
+                // on rescans, only update sensors we were signaled by
+                auto findSensor = sensors.find(sensorName);
+                if (!firstScan && findSensor != sensors.end())
+                {
+                    bool found = false;
+                    auto it = sensorsChanged->begin();
+                    while (it != sensorsChanged->end())
+                    {
+                        if (it->ends_with(findSensor->second->name))
+                        {
+                            it = sensorsChanged->erase(it);
+                            findSensor->second = nullptr;
+                            found = true;
+                            break;
+                        }
+                        ++it;
+                    }
+                    if (!found)
+                    {
+                        continue;
+                    }
+                }
+
+                std::vector<thresholds::Threshold> sensorThresholds;
+
+                if (!parseThresholdsFromConfig(sensorData, sensorThresholds,
+                                               nullptr, &index))
+                {
+                    std::cerr << "error populating thresholds for "
+                              << sensorName << " index " << index << "\n";
+                }
+
+                float pollRate = getPollRate(baseConfigMap, pollRateDefault);
+                PowerState readState = getPowerState(baseConfigMap);
+
+                auto permitSet = getPermitSet(baseConfigMap);
+                auto& sensor = sensors[sensorName];
+                if (!activateOnly)
+                {
+                    sensor = nullptr;
+                }
+                auto hwmonFile = getFullHwmonFilePath(directory.string(),
+                                                      "temp1", permitSet);
+                if (pathStr.starts_with("/sys/bus/iio/devices"))
+                {
+                    hwmonFile = pathStr;
+                }
+                if (hwmonFile)
+                {
+                    if (sensor != nullptr)
+                    {
+                        sensor->activate(*hwmonFile, i2cDev);
+                    }
+                    else
+                    {
+                        sensor = std::make_shared<HwmonTempSensor>(
+                            *hwmonFile, sensorType, objectServer,
+                            dbusConnection, io, sensorName,
+                            std::move(sensorThresholds), thisSensorParameters,
+                            pollRate, interfacePath, readState, i2cDev);
+                        sensor->setupRead();
+                    }
+                }
+                hwmonName.erase(
+                    remove(hwmonName.begin(), hwmonName.end(), sensorName),
+                    hwmonName.end());
+
+                // Looking for keys like "Name1" for temp2_input,
+                // "Name2" for temp3_input, etc.
+                int i = 0;
+                while (true)
+                {
+                    ++i;
+                    auto findKey =
+                        baseConfigMap.find("Name" + std::to_string(i));
+                    if (findKey == baseConfigMap.end())
+                    {
+                        break;
+                    }
+                    std::string sensorName =
+                        std::get<std::string>(findKey->second);
+                    hwmonFile = getFullHwmonFilePath(
+                        directory.string(), "temp" + std::to_string(i + 1),
+                        permitSet);
+                    if (pathStr.starts_with("/sys/bus/iio/devices"))
+                    {
+                        continue;
+                    }
+                    if (hwmonFile)
+                    {
+                        // To look up thresholds for these additional sensors,
+                        // match on the Index property in the threshold data
+                        // where the index comes from the sysfs file we're on,
+                        // i.e. index = 2 for temp2_input.
+                        int index = i + 1;
+                        std::vector<thresholds::Threshold> thresholds;
+
+                        if (!parseThresholdsFromConfig(sensorData, thresholds,
+                                                       nullptr, &index))
+                        {
+                            std::cerr
+                                << "error populating thresholds for "
+                                << sensorName << " index " << index << "\n";
+                        }
+
+                        auto& sensor = sensors[sensorName];
+                        if (!activateOnly)
+                        {
+                            sensor = nullptr;
+                        }
+
+                        if (sensor != nullptr)
+                        {
+                            sensor->activate(*hwmonFile, i2cDev);
+                        }
+                        else
+                        {
+                            sensor = std::make_shared<HwmonTempSensor>(
+                                *hwmonFile, sensorType, objectServer,
+                                dbusConnection, io, sensorName,
+                                std::move(thresholds), thisSensorParameters,
+                                pollRate, interfacePath, readState, i2cDev);
+                            sensor->setupRead();
+                        }
+                    }
+
+                    hwmonName.erase(
+                        remove(hwmonName.begin(), hwmonName.end(), sensorName),
+                        hwmonName.end());
+                }
+                if (hwmonName.empty())
+                {
+                    configMap.erase(findSensorCfg);
+                }
+            }
+        });
+    std::vector<std::string> types(sensorTypes.size());
+    for (const auto& [type, dt] : sensorTypes)
+    {
+        types.push_back(type);
+    }
+    getter->getConfiguration(types);
+}
+
+void interfaceRemoved(
+    sdbusplus::message_t& message,
+    boost::container::flat_map<std::string, std::shared_ptr<HwmonTempSensor>>&
+        sensors)
+{
+    if (message.is_method_error())
+    {
+        std::cerr << "interfacesRemoved callback method error\n";
+        return;
+    }
+
+    sdbusplus::message::object_path path;
+    std::vector<std::string> interfaces;
+
+    message.read(path, interfaces);
+
+    // If the xyz.openbmc_project.Confguration.X interface was removed
+    // for one or more sensors, delete those sensor objects.
+    auto sensorIt = sensors.begin();
+    while (sensorIt != sensors.end())
+    {
+        if (sensorIt->second && (sensorIt->second->configurationPath == path) &&
+            (std::find(interfaces.begin(), interfaces.end(),
+                       sensorIt->second->configInterface) != interfaces.end()))
+        {
+            sensorIt = sensors.erase(sensorIt);
+        }
+        else
+        {
+            sensorIt++;
+        }
+    }
+}
+
+static void powerStateChanged(
+    PowerState type, bool newState,
+    boost::container::flat_map<std::string, std::shared_ptr<HwmonTempSensor>>&
+        sensors,
+    boost::asio::io_context& io, sdbusplus::asio::object_server& objectServer,
+    std::shared_ptr<sdbusplus::asio::connection>& dbusConnection)
+{
+    if (newState)
+    {
+        createSensors(io, objectServer, sensors, dbusConnection, nullptr, true);
+    }
+    else
+    {
+        for (auto& [path, sensor] : sensors)
+        {
+            if (sensor != nullptr && sensor->readState == type)
+            {
+                sensor->deactivate();
+            }
+        }
+    }
+}
+
+int main()
+{
+    boost::asio::io_context io;
+    auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
+    sdbusplus::asio::object_server objectServer(systemBus, true);
+    objectServer.add_manager("/xyz/openbmc_project/sensors");
+    systemBus->request_name("xyz.openbmc_project.HwmonTempSensor");
+
+    boost::container::flat_map<std::string, std::shared_ptr<HwmonTempSensor>>
+        sensors;
+    auto sensorsChanged =
+        std::make_shared<boost::container::flat_set<std::string>>();
+
+    auto powerCallBack = [&sensors, &io, &objectServer,
+                          &systemBus](PowerState type, bool state) {
+        powerStateChanged(type, state, sensors, io, objectServer, systemBus);
+    };
+    setupPowerMatchCallback(systemBus, powerCallBack);
+
+    boost::asio::post(io, [&]() {
+        createSensors(io, objectServer, sensors, systemBus, nullptr, false);
+    });
+
+    boost::asio::steady_timer filterTimer(io);
+    std::function<void(sdbusplus::message_t&)> eventHandler =
+        [&](sdbusplus::message_t& 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_after(std::chrono::seconds(1));
+
+            filterTimer.async_wait([&](const boost::system::error_code& ec) {
+                if (ec == boost::asio::error::operation_aborted)
+                {
+                    /* we were canceled*/
+                    return;
+                }
+                if (ec)
+                {
+                    std::cerr << "timer error\n";
+                    return;
+                }
+                createSensors(io, objectServer, sensors, systemBus,
+                              sensorsChanged, false);
+            });
+        };
+
+    std::vector<std::unique_ptr<sdbusplus::bus::match_t>> matches =
+        setupPropertiesChangedMatches(*systemBus, sensorTypes, eventHandler);
+    setupManufacturingModeMatch(*systemBus);
+
+    // Watch for entity-manager to remove configuration interfaces
+    // so the corresponding sensors can be removed.
+    auto ifaceRemovedMatch = std::make_unique<sdbusplus::bus::match_t>(
+        static_cast<sdbusplus::bus_t&>(*systemBus),
+        "type='signal',member='InterfacesRemoved',arg0path='" +
+            std::string(inventoryPath) + "/'",
+        [&sensors](sdbusplus::message_t& msg) {
+            interfaceRemoved(msg, sensors);
+        });
+
+    matches.emplace_back(std::move(ifaceRemovedMatch));
+
+    io.run();
+}
diff --git a/src/hwmon-temp/HwmonTempSensor.cpp b/src/hwmon-temp/HwmonTempSensor.cpp
new file mode 100644
index 0000000..eab7349
--- /dev/null
+++ b/src/hwmon-temp/HwmonTempSensor.cpp
@@ -0,0 +1,206 @@
+/*
+// Copyright (c) 2017 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 "HwmonTempSensor.hpp"
+
+#include "DeviceMgmt.hpp"
+#include "Thresholds.hpp"
+#include "Utils.hpp"
+#include "sensor.hpp"
+
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/asio/buffer.hpp>
+#include <boost/asio/error.hpp>
+#include <boost/asio/io_context.hpp>
+#include <boost/asio/random_access_file.hpp>
+#include <sdbusplus/asio/connection.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <charconv>
+#include <chrono>
+#include <cstddef>
+#include <iostream>
+#include <limits>
+#include <memory>
+#include <string>
+#include <system_error>
+#include <utility>
+#include <vector>
+
+// Temperatures are read in milli degrees Celsius, we need degrees Celsius.
+// Pressures are read in kilopascal, we need Pascals.  On D-Bus for Open BMC
+// we use the International System of Units without prefixes.
+// Links to the kernel documentation:
+// https://www.kernel.org/doc/Documentation/hwmon/sysfs-interface
+// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-iio
+// For IIO RAW sensors we get a raw_value, an offset, and scale to compute
+// the value = (raw_value + offset) * scale
+
+HwmonTempSensor::HwmonTempSensor(
+    const std::string& path, const std::string& objectType,
+    sdbusplus::asio::object_server& objectServer,
+    std::shared_ptr<sdbusplus::asio::connection>& conn,
+    boost::asio::io_context& io, const std::string& sensorName,
+    std::vector<thresholds::Threshold>&& thresholdsIn,
+    const struct SensorParams& thisSensorParameters, const float pollRate,
+    const std::string& sensorConfiguration, const PowerState powerState,
+    const std::shared_ptr<I2CDevice>& i2cDevice) :
+    Sensor(boost::replace_all_copy(sensorName, " ", "_"),
+           std::move(thresholdsIn), sensorConfiguration, objectType, false,
+           false, thisSensorParameters.maxValue, thisSensorParameters.minValue,
+           conn, powerState),
+    i2cDevice(i2cDevice), objServer(objectServer),
+    inputDev(io, path, boost::asio::random_access_file::read_only),
+    waitTimer(io), path(path), offsetValue(thisSensorParameters.offsetValue),
+    scaleValue(thisSensorParameters.scaleValue),
+    sensorPollMs(static_cast<unsigned int>(pollRate * 1000))
+{
+    sensorInterface = objectServer.add_interface(
+        "/xyz/openbmc_project/sensors/" + thisSensorParameters.typeName + "/" +
+            name,
+        "xyz.openbmc_project.Sensor.Value");
+
+    for (const auto& threshold : thresholds)
+    {
+        std::string interface = thresholds::getInterface(threshold.level);
+        thresholdInterfaces[static_cast<size_t>(threshold.level)] =
+            objectServer.add_interface(
+                "/xyz/openbmc_project/sensors/" +
+                    thisSensorParameters.typeName + "/" + name,
+                interface);
+    }
+    association = objectServer.add_interface(
+        "/xyz/openbmc_project/sensors/" + thisSensorParameters.typeName + "/" +
+            name,
+        association::interface);
+    setInitialProperties(thisSensorParameters.units);
+}
+
+bool HwmonTempSensor::isActive()
+{
+    return inputDev.is_open();
+}
+
+void HwmonTempSensor::activate(const std::string& newPath,
+                               const std::shared_ptr<I2CDevice>& newI2CDevice)
+{
+    path = newPath;
+    i2cDevice = newI2CDevice;
+    inputDev.open(path, boost::asio::random_access_file::read_only);
+    markAvailable(true);
+    setupRead();
+}
+
+void HwmonTempSensor::deactivate()
+{
+    markAvailable(false);
+    // close the input dev to cancel async operations
+    inputDev.close();
+    waitTimer.cancel();
+    i2cDevice = nullptr;
+    path = "";
+}
+
+HwmonTempSensor::~HwmonTempSensor()
+{
+    deactivate();
+
+    for (const auto& iface : thresholdInterfaces)
+    {
+        objServer.remove_interface(iface);
+    }
+    objServer.remove_interface(sensorInterface);
+    objServer.remove_interface(association);
+}
+
+void HwmonTempSensor::setupRead()
+{
+    if (!readingStateGood())
+    {
+        markAvailable(false);
+        updateValue(std::numeric_limits<double>::quiet_NaN());
+        restartRead();
+        return;
+    }
+
+    std::weak_ptr<HwmonTempSensor> weakRef = weak_from_this();
+    inputDev.async_read_some_at(
+        0, boost::asio::buffer(readBuf),
+        [weakRef](const boost::system::error_code& ec, std::size_t bytesRead) {
+            std::shared_ptr<HwmonTempSensor> self = weakRef.lock();
+            if (self)
+            {
+                self->handleResponse(ec, bytesRead);
+            }
+        });
+}
+
+void HwmonTempSensor::restartRead()
+{
+    std::weak_ptr<HwmonTempSensor> weakRef = weak_from_this();
+    waitTimer.expires_after(std::chrono::milliseconds(sensorPollMs));
+    waitTimer.async_wait([weakRef](const boost::system::error_code& ec) {
+        if (ec == boost::asio::error::operation_aborted)
+        {
+            return; // we're being canceled
+        }
+        std::shared_ptr<HwmonTempSensor> self = weakRef.lock();
+        if (!self)
+        {
+            return;
+        }
+        self->setupRead();
+    });
+}
+
+void HwmonTempSensor::handleResponse(const boost::system::error_code& err,
+                                     size_t bytesRead)
+{
+    if ((err == boost::system::errc::bad_file_descriptor) ||
+        (err == boost::asio::error::misc_errors::not_found))
+    {
+        std::cerr << "Hwmon temp sensor " << name << " removed " << path
+                  << "\n";
+        return; // we're being destroyed
+    }
+
+    if (!err)
+    {
+        const char* bufEnd = readBuf.data() + bytesRead;
+        int nvalue = 0;
+        std::from_chars_result ret =
+            std::from_chars(readBuf.data(), bufEnd, nvalue);
+        if (ret.ec != std::errc())
+        {
+            incrementError();
+        }
+        else
+        {
+            updateValue((nvalue + offsetValue) * scaleValue);
+        }
+    }
+    else
+    {
+        incrementError();
+    }
+
+    restartRead();
+}
+
+void HwmonTempSensor::checkThresholds()
+{
+    thresholds::checkThresholds(this);
+}
diff --git a/src/hwmon-temp/HwmonTempSensor.hpp b/src/hwmon-temp/HwmonTempSensor.hpp
new file mode 100644
index 0000000..7208090
--- /dev/null
+++ b/src/hwmon-temp/HwmonTempSensor.hpp
@@ -0,0 +1,65 @@
+#pragma once
+
+#include "DeviceMgmt.hpp"
+#include "Thresholds.hpp"
+#include "sensor.hpp"
+
+#include <boost/asio/random_access_file.hpp>
+#include <sdbusplus/asio/object_server.hpp>
+
+#include <string>
+#include <vector>
+
+struct SensorParams
+{
+    double minValue;
+    double maxValue;
+    double offsetValue;
+    double scaleValue;
+    std::string units;
+    std::string typeName;
+};
+
+class HwmonTempSensor :
+    public Sensor,
+    public std::enable_shared_from_this<HwmonTempSensor>
+{
+  public:
+    HwmonTempSensor(const std::string& path, const std::string& objectType,
+                    sdbusplus::asio::object_server& objectServer,
+                    std::shared_ptr<sdbusplus::asio::connection>& conn,
+                    boost::asio::io_context& io, const std::string& sensorName,
+                    std::vector<thresholds::Threshold>&& thresholds,
+                    const struct SensorParams& thisSensorParameters,
+                    float pollRate, const std::string& sensorConfiguration,
+                    PowerState powerState,
+                    const std::shared_ptr<I2CDevice>& i2cDevice);
+    ~HwmonTempSensor() override;
+    void setupRead();
+    void activate(const std::string& newPath,
+                  const std::shared_ptr<I2CDevice>& newI2CDevice);
+    void deactivate();
+    bool isActive();
+
+    std::shared_ptr<I2CDevice> getI2CDevice() const
+    {
+        return i2cDevice;
+    }
+
+  private:
+    // Ordering is important here; readBuf is first so that it's not destroyed
+    // while async operations from other member fields might still be using it.
+    std::array<char, 128> readBuf{};
+    std::shared_ptr<I2CDevice> i2cDevice;
+    sdbusplus::asio::object_server& objServer;
+    boost::asio::random_access_file inputDev;
+    boost::asio::steady_timer waitTimer;
+    std::string path;
+    double offsetValue;
+    double scaleValue;
+    unsigned int sensorPollMs;
+
+    void handleResponse(const boost::system::error_code& err, size_t bytesRead);
+    void restartRead();
+    void checkThresholds() override;
+};
diff --git a/src/hwmon-temp/meson.build b/src/hwmon-temp/meson.build
new file mode 100644
index 0000000..3149960
--- /dev/null
+++ b/src/hwmon-temp/meson.build
@@ -0,0 +1,15 @@
+src_inc = include_directories('..')
+
+executable(
+    'hwmontempsensor',
+    'HwmonTempMain.cpp',
+    'HwmonTempSensor.cpp',
+    dependencies: [
+        default_deps,
+        devicemgmt_dep,
+        thresholds_dep,
+        utils_dep,
+    ],
+    include_directories: src_inc,
+    install: true,
+)
\ No newline at end of file