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