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