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