xref: /openbmc/dbus-sensors/src/external/ExternalSensor.cpp (revision d85c89518604abc63f9f0121f334277234271dfb)
1 #include "ExternalSensor.hpp"
2 
3 #include "SensorPaths.hpp"
4 #include "Thresholds.hpp"
5 #include "Utils.hpp"
6 #include "sensor.hpp"
7 
8 #include <phosphor-logging/lg2.hpp>
9 #include <sdbusplus/asio/connection.hpp>
10 #include <sdbusplus/asio/object_server.hpp>
11 
12 #include <chrono>
13 #include <cmath>
14 #include <cstddef>
15 #include <functional>
16 #include <limits>
17 #include <memory>
18 #include <stdexcept>
19 #include <string>
20 #include <utility>
21 #include <vector>
22 
ExternalSensor(const std::string & objectType,sdbusplus::asio::object_server & objectServer,std::shared_ptr<sdbusplus::asio::connection> & conn,const std::string & sensorName,const std::string & sensorUnits,std::vector<thresholds::Threshold> && thresholdsIn,const std::string & sensorConfiguration,double maxReading,double minReading,double timeoutSecs,const PowerState & powerState)23 ExternalSensor::ExternalSensor(
24     const std::string& objectType, sdbusplus::asio::object_server& objectServer,
25     std::shared_ptr<sdbusplus::asio::connection>& conn,
26     const std::string& sensorName, const std::string& sensorUnits,
27     std::vector<thresholds::Threshold>&& thresholdsIn,
28     const std::string& sensorConfiguration, double maxReading,
29     double minReading, double timeoutSecs, const PowerState& powerState) :
30     Sensor(escapeName(sensorName), std::move(thresholdsIn), sensorConfiguration,
31            objectType, true, true, maxReading, minReading, conn, powerState),
32     objServer(objectServer), writeLast(std::chrono::steady_clock::now()),
33     writeTimeout(
34         std::chrono::duration_cast<std::chrono::steady_clock::duration>(
35             std::chrono::duration<double>(timeoutSecs))),
36     writePerishable(timeoutSecs > 0.0)
37 {
38     // The caller must specify what physical characteristic
39     // an external sensor is expected to be measuring, such as temperature,
40     // as, unlike others, this is not implied by device type name.
41     std::string dbusPath = sensor_paths::getPathForUnits(sensorUnits);
42     if (dbusPath.empty())
43     {
44         throw std::runtime_error("Units not in allow list");
45     }
46     std::string objectPath = "/xyz/openbmc_project/sensors/";
47     objectPath += dbusPath;
48     objectPath += '/';
49     objectPath += name;
50 
51     sensorInterface = objectServer.add_interface(
52         objectPath, "xyz.openbmc_project.Sensor.Value");
53 
54     for (const auto& threshold : thresholds)
55     {
56         std::string interface = thresholds::getInterface(threshold.level);
57         thresholdInterfaces[static_cast<size_t>(threshold.level)] =
58             objectServer.add_interface(objectPath, interface);
59     }
60 
61     association =
62         objectServer.add_interface(objectPath, association::interface);
63     std::string fullUnits = sensor_paths::convertToFullUnits(sensorUnits);
64     if (fullUnits.empty())
65     {
66         lg2::error("Invalid units for sensor: {UNITS}", "UNITS", sensorUnits);
67         throw std::invalid_argument("Invalid units for sensor");
68     }
69     setInitialProperties(fullUnits);
70 
71     lg2::debug(
72         "ExternalSensor '{NAME}' constructed: path '{PATH}', type '{TYPE}', "
73         "min '{MIN}', max '{MAX}', timeout '{TIMEOUT}' us",
74         "NAME", name, "PATH", objectPath, "TYPE", objectType, "MIN", minReading,
75         "MAX", maxReading, "TIMEOUT",
76         std::chrono::duration_cast<std::chrono::microseconds>(writeTimeout)
77             .count());
78 }
79 
80 // Separate function from constructor, because of a gotcha: can't use the
81 // enable_shared_from_this() API until after the constructor has completed.
initWriteHook(std::function<void (std::chrono::steady_clock::time_point now)> && writeHookIn)82 void ExternalSensor::initWriteHook(
83     std::function<void(std::chrono::steady_clock::time_point now)>&&
84         writeHookIn)
85 {
86     // Connect ExternalSensorMain with ExternalSensor
87     writeHook = std::move(writeHookIn);
88 
89     // Connect ExternalSensor with Sensor
90     auto weakThis = weak_from_this();
91     externalSetHook = [weakThis]() {
92         auto lockThis = weakThis.lock();
93         if (lockThis)
94         {
95             lockThis->externalSetTrigger();
96             return;
97         }
98         lg2::debug("ExternalSensor receive ignored, sensor gone");
99     };
100 }
101 
~ExternalSensor()102 ExternalSensor::~ExternalSensor()
103 {
104     // Make sure the write hook does not reference this object anymore
105     externalSetHook = nullptr;
106 
107     objServer.remove_interface(association);
108     for (const auto& iface : thresholdInterfaces)
109     {
110         objServer.remove_interface(iface);
111     }
112     objServer.remove_interface(sensorInterface);
113 
114     lg2::debug("ExternalSensor '{NAME}' destructed", "NAME", name);
115 }
116 
checkThresholds()117 void ExternalSensor::checkThresholds()
118 {
119     thresholds::checkThresholds(this);
120 }
121 
isAliveAndPerishable() const122 bool ExternalSensor::isAliveAndPerishable() const
123 {
124     return (writeAlive && writePerishable);
125 }
126 
isAliveAndFresh(const std::chrono::steady_clock::time_point & now) const127 bool ExternalSensor::isAliveAndFresh(
128     const std::chrono::steady_clock::time_point& now) const
129 {
130     // Must be alive and perishable, to have possibility of being fresh
131     if (!isAliveAndPerishable())
132     {
133         return false;
134     }
135 
136     // If age, as of now, is less than timeout, it is deemed fresh
137     // NOLINTNEXTLINE
138     return (ageElapsed(now) < writeTimeout);
139 }
140 
writeBegin(const std::chrono::steady_clock::time_point & now)141 void ExternalSensor::writeBegin(
142     const std::chrono::steady_clock::time_point& now)
143 {
144     if (!writeAlive)
145     {
146         lg2::error(
147             "ExternalSensor '{NAME}' online, receiving first value '{VALUE}'",
148             "NAME", name, "VALUE", value);
149     }
150 
151     writeLast = now;
152     writeAlive = true;
153 }
154 
writeInvalidate()155 void ExternalSensor::writeInvalidate()
156 {
157     writeAlive = false;
158 
159     lg2::error("ExternalSensor '{NAME}' offline, timed out", "NAME", name);
160 
161     // Take back control of this sensor from the external override,
162     // as the external source has timed out.
163     // This allows sensor::updateValue() to work normally,
164     // as it would do for internal sensors with values from hardware.
165     overriddenState = false;
166 
167     // Invalidate the existing Value, similar to what internal sensors do,
168     // when they encounter errors trying to read from hardware.
169     updateValue(std::numeric_limits<double>::quiet_NaN());
170 }
171 
ageElapsed(const std::chrono::steady_clock::time_point & now) const172 std::chrono::steady_clock::duration ExternalSensor::ageElapsed(
173     const std::chrono::steady_clock::time_point& now) const
174 {
175     // Comparing 2 time_point will return duration
176     return (now - writeLast);
177 }
178 
ageRemaining(const std::chrono::steady_clock::time_point & now) const179 std::chrono::steady_clock::duration ExternalSensor::ageRemaining(
180     const std::chrono::steady_clock::time_point& now) const
181 {
182     // Comparing duration will return another duration
183     return (writeTimeout - ageElapsed(now));
184 }
185 
externalSetTrigger()186 void ExternalSensor::externalSetTrigger()
187 {
188     lg2::debug("ExternalSensor '{NAME}' received '{VALUE}'", "NAME", name,
189                "VALUE", value);
190 
191     if (std::isfinite(value))
192     {
193         markAvailable(true);
194     }
195 
196     auto now = std::chrono::steady_clock::now();
197 
198     writeBegin(now);
199 
200     // Tell the owner to recalculate the expiration timer
201     writeHook(now);
202 }
203