12a40e939SJosh Lehan #include "ExternalSensor.hpp"
22a40e939SJosh Lehan 
32a40e939SJosh Lehan #include "SensorPaths.hpp"
42a40e939SJosh Lehan 
52a40e939SJosh Lehan #include <unistd.h>
62a40e939SJosh Lehan 
72a40e939SJosh Lehan #include <boost/algorithm/string/predicate.hpp>
82a40e939SJosh Lehan #include <boost/algorithm/string/replace.hpp>
92a40e939SJosh Lehan #include <boost/date_time/posix_time/posix_time.hpp>
102a40e939SJosh Lehan #include <sdbusplus/asio/connection.hpp>
112a40e939SJosh Lehan #include <sdbusplus/asio/object_server.hpp>
122a40e939SJosh Lehan 
137243217bSJosh Lehan #include <chrono>
142a40e939SJosh Lehan #include <iostream>
152a40e939SJosh Lehan #include <istream>
162a40e939SJosh Lehan #include <limits>
172a40e939SJosh Lehan #include <memory>
182a40e939SJosh Lehan #include <string>
192a40e939SJosh Lehan #include <vector>
202a40e939SJosh Lehan 
217243217bSJosh Lehan static constexpr bool debug = false;
227243217bSJosh Lehan 
232a40e939SJosh Lehan ExternalSensor::ExternalSensor(
242a40e939SJosh Lehan     const std::string& objectType, sdbusplus::asio::object_server& objectServer,
252a40e939SJosh Lehan     std::shared_ptr<sdbusplus::asio::connection>& conn,
262a40e939SJosh Lehan     const std::string& sensorName, const std::string& sensorUnits,
277b7a9deaSJeff Lin     std::vector<thresholds::Threshold>&& thresholdsIn,
287243217bSJosh Lehan     const std::string& sensorConfiguration, double maxReading,
290362738dSJosh Lehan     double minReading, double timeoutSecs, const PowerState& powerState) :
302a40e939SJosh Lehan     // TODO(): When the Mutable feature is integrated,
312a40e939SJosh Lehan     // make sure all ExternalSensor instances are mutable,
322a40e939SJosh Lehan     // because that is the entire point of ExternalSensor,
332a40e939SJosh Lehan     // to accept sensor values written by an external source.
347b7a9deaSJeff Lin     Sensor(boost::replace_all_copy(sensorName, " ", "_"),
35*1263c3daSBruce Lee            std::move(thresholdsIn), sensorConfiguration, objectType, true,
36*1263c3daSBruce Lee            maxReading, minReading, conn, powerState),
377243217bSJosh Lehan     std::enable_shared_from_this<ExternalSensor>(), objServer(objectServer),
387243217bSJosh Lehan     writeLast(std::chrono::steady_clock::now()),
397243217bSJosh Lehan     writeTimeout(
407243217bSJosh Lehan         std::chrono::duration_cast<std::chrono::steady_clock::duration>(
417243217bSJosh Lehan             std::chrono::duration<double>(timeoutSecs))),
420362738dSJosh Lehan     writeAlive(false), writePerishable(timeoutSecs > 0.0)
432a40e939SJosh Lehan {
442a40e939SJosh Lehan     // The caller must specify what physical characteristic
452a40e939SJosh Lehan     // an external sensor is expected to be measuring, such as temperature,
462a40e939SJosh Lehan     // as, unlike others, this is not implied by device type name.
476cb732a3SEd Tanous     std::string dbusPath = sensor_paths::getPathForUnits(sensorUnits);
482a40e939SJosh Lehan     if (dbusPath.empty())
492a40e939SJosh Lehan     {
502a40e939SJosh Lehan         throw std::runtime_error("Units not in allow list");
512a40e939SJosh Lehan     }
526cb732a3SEd Tanous     std::string objectPath = "/xyz/openbmc_project/sensors/";
532a40e939SJosh Lehan     objectPath += dbusPath;
542a40e939SJosh Lehan     objectPath += '/';
552a40e939SJosh Lehan     objectPath += sensorName;
562a40e939SJosh Lehan 
572a40e939SJosh Lehan     sensorInterface = objectServer.add_interface(
582a40e939SJosh Lehan         objectPath, "xyz.openbmc_project.Sensor.Value");
592a40e939SJosh Lehan 
602a40e939SJosh Lehan     if (thresholds::hasWarningInterface(thresholds))
612a40e939SJosh Lehan     {
622a40e939SJosh Lehan         thresholdInterfaceWarning = objectServer.add_interface(
632a40e939SJosh Lehan             objectPath, "xyz.openbmc_project.Sensor.Threshold.Warning");
642a40e939SJosh Lehan     }
652a40e939SJosh Lehan     if (thresholds::hasCriticalInterface(thresholds))
662a40e939SJosh Lehan     {
672a40e939SJosh Lehan         thresholdInterfaceCritical = objectServer.add_interface(
682a40e939SJosh Lehan             objectPath, "xyz.openbmc_project.Sensor.Threshold.Critical");
692a40e939SJosh Lehan     }
702a40e939SJosh Lehan 
712a40e939SJosh Lehan     association =
722a40e939SJosh Lehan         objectServer.add_interface(objectPath, association::interface);
736b6891c5SZev Weiss     setInitialProperties(conn, sensorUnits);
747243217bSJosh Lehan 
757243217bSJosh Lehan     if constexpr (debug)
767243217bSJosh Lehan     {
777243217bSJosh Lehan         std::cerr << "ExternalSensor " << name << " constructed: path "
787243217bSJosh Lehan                   << configurationPath << ", type " << objectType << ", min "
797243217bSJosh Lehan                   << minReading << ", max " << maxReading << ", timeout "
807243217bSJosh Lehan                   << std::chrono::duration_cast<std::chrono::microseconds>(
817243217bSJosh Lehan                          writeTimeout)
827243217bSJosh Lehan                          .count()
837243217bSJosh Lehan                   << " us\n";
847243217bSJosh Lehan     }
852a40e939SJosh Lehan }
862a40e939SJosh Lehan 
870362738dSJosh Lehan // Separate function from constructor, because of a gotcha: can't use the
880362738dSJosh Lehan // enable_shared_from_this() API until after the constructor has completed.
890362738dSJosh Lehan void ExternalSensor::initWriteHook(
900362738dSJosh Lehan     std::function<void(std::chrono::steady_clock::time_point now)>&&
910362738dSJosh Lehan         writeHookIn)
920362738dSJosh Lehan {
930362738dSJosh Lehan     // Connect ExternalSensorMain with ExternalSensor
940362738dSJosh Lehan     writeHook = std::move(writeHookIn);
950362738dSJosh Lehan 
960362738dSJosh Lehan     // Connect ExternalSensor with Sensor
970362738dSJosh Lehan     auto weakThis = weak_from_this();
980362738dSJosh Lehan     externalSetHook = std::move([weakThis]() {
990362738dSJosh Lehan         auto lockThis = weakThis.lock();
1000362738dSJosh Lehan         if (lockThis)
1010362738dSJosh Lehan         {
1020362738dSJosh Lehan             lockThis->externalSetTrigger();
1030362738dSJosh Lehan             return;
1040362738dSJosh Lehan         }
1050362738dSJosh Lehan         if constexpr (debug)
1060362738dSJosh Lehan         {
1070362738dSJosh Lehan             std::cerr << "ExternalSensor receive ignored, sensor gone\n";
1080362738dSJosh Lehan         }
1090362738dSJosh Lehan     });
1100362738dSJosh Lehan }
1110362738dSJosh Lehan 
1122a40e939SJosh Lehan ExternalSensor::~ExternalSensor()
1132a40e939SJosh Lehan {
1147243217bSJosh Lehan     // Make sure the write hook does not reference this object anymore
1157243217bSJosh Lehan     externalSetHook = nullptr;
1167243217bSJosh Lehan 
1172a40e939SJosh Lehan     objServer.remove_interface(association);
1182a40e939SJosh Lehan     objServer.remove_interface(thresholdInterfaceCritical);
1192a40e939SJosh Lehan     objServer.remove_interface(thresholdInterfaceWarning);
1202a40e939SJosh Lehan     objServer.remove_interface(sensorInterface);
1217243217bSJosh Lehan 
1227243217bSJosh Lehan     if constexpr (debug)
1237243217bSJosh Lehan     {
1247243217bSJosh Lehan         std::cerr << "ExternalSensor " << name << " destructed\n";
1257243217bSJosh Lehan     }
1262a40e939SJosh Lehan }
1272a40e939SJosh Lehan 
1282a40e939SJosh Lehan void ExternalSensor::checkThresholds(void)
1292a40e939SJosh Lehan {
1302a40e939SJosh Lehan     thresholds::checkThresholds(this);
1312a40e939SJosh Lehan }
1327243217bSJosh Lehan 
1337243217bSJosh Lehan bool ExternalSensor::isAliveAndPerishable(void) const
1347243217bSJosh Lehan {
1357243217bSJosh Lehan     return (writeAlive && writePerishable);
1367243217bSJosh Lehan }
1377243217bSJosh Lehan 
1387243217bSJosh Lehan bool ExternalSensor::isAliveAndFresh(
1397243217bSJosh Lehan     const std::chrono::steady_clock::time_point& now) const
1407243217bSJosh Lehan {
1417243217bSJosh Lehan     // Must be alive and perishable, to have possibility of being fresh
1427243217bSJosh Lehan     if (!isAliveAndPerishable())
1437243217bSJosh Lehan     {
1447243217bSJosh Lehan         return false;
1457243217bSJosh Lehan     }
1467243217bSJosh Lehan 
1477243217bSJosh Lehan     // If age, as of now, is less than timeout, it is deemed fresh
14892b9629aSAndrew Jeffery     // NOLINTNEXTLINE
1497243217bSJosh Lehan     return (ageElapsed(now) < writeTimeout);
1507243217bSJosh Lehan }
1517243217bSJosh Lehan 
1527243217bSJosh Lehan void ExternalSensor::writeBegin(
1537243217bSJosh Lehan     const std::chrono::steady_clock::time_point& now)
1547243217bSJosh Lehan {
1557243217bSJosh Lehan     if (!writeAlive)
1567243217bSJosh Lehan     {
1577243217bSJosh Lehan         std::cerr << "ExternalSensor " << name
1587243217bSJosh Lehan                   << " online, receiving first value " << value << "\n";
1597243217bSJosh Lehan     }
1607243217bSJosh Lehan 
1617243217bSJosh Lehan     writeLast = now;
1627243217bSJosh Lehan     writeAlive = true;
1637243217bSJosh Lehan }
1647243217bSJosh Lehan 
1657243217bSJosh Lehan void ExternalSensor::writeInvalidate(void)
1667243217bSJosh Lehan {
1677243217bSJosh Lehan     writeAlive = false;
1687243217bSJosh Lehan 
1697243217bSJosh Lehan     std::cerr << "ExternalSensor " << name << " offline, timed out\n";
1707243217bSJosh Lehan 
1717243217bSJosh Lehan     // Take back control of this sensor from the external override,
1727243217bSJosh Lehan     // as the external source has timed out.
1737243217bSJosh Lehan     // This allows sensor::updateValue() to work normally,
1747243217bSJosh Lehan     // as it would do for internal sensors with values from hardware.
1757243217bSJosh Lehan     overriddenState = false;
1767243217bSJosh Lehan 
1777243217bSJosh Lehan     // Invalidate the existing Value, similar to what internal sensors do,
1787243217bSJosh Lehan     // when they encounter errors trying to read from hardware.
1797243217bSJosh Lehan     updateValue(std::numeric_limits<double>::quiet_NaN());
1807243217bSJosh Lehan }
1817243217bSJosh Lehan 
1827243217bSJosh Lehan std::chrono::steady_clock::duration ExternalSensor::ageElapsed(
1837243217bSJosh Lehan     const std::chrono::steady_clock::time_point& now) const
1847243217bSJosh Lehan {
1857243217bSJosh Lehan     // Comparing 2 time_point will return duration
1867243217bSJosh Lehan     return (now - writeLast);
1877243217bSJosh Lehan }
1887243217bSJosh Lehan 
1897243217bSJosh Lehan std::chrono::steady_clock::duration ExternalSensor::ageRemaining(
1907243217bSJosh Lehan     const std::chrono::steady_clock::time_point& now) const
1917243217bSJosh Lehan {
1927243217bSJosh Lehan     // Comparing duration will return another duration
1937243217bSJosh Lehan     return (writeTimeout - ageElapsed(now));
1947243217bSJosh Lehan }
1957243217bSJosh Lehan 
1967243217bSJosh Lehan void ExternalSensor::externalSetTrigger(void)
1977243217bSJosh Lehan {
1987243217bSJosh Lehan     if constexpr (debug)
1997243217bSJosh Lehan     {
2007243217bSJosh Lehan         std::cerr << "ExternalSensor " << name << " received " << value << "\n";
2017243217bSJosh Lehan     }
2027243217bSJosh Lehan 
2037243217bSJosh Lehan     auto now = std::chrono::steady_clock::now();
2047243217bSJosh Lehan 
2057243217bSJosh Lehan     writeBegin(now);
2067243217bSJosh Lehan 
2077243217bSJosh Lehan     // Tell the owner to recalculate the expiration timer
2087243217bSJosh Lehan     writeHook(now);
2097243217bSJosh Lehan }
210