blob: 19b8f607afa4e3e5c6156218c30054857cb6c0da [file] [log] [blame]
Josh Lehan2a40e932020-09-02 11:48:14 -07001#include "ExternalSensor.hpp"
2#include "Utils.hpp"
3#include "VariantVisitors.hpp"
4
5#include <boost/algorithm/string/predicate.hpp>
6#include <boost/algorithm/string/replace.hpp>
7#include <boost/container/flat_map.hpp>
8#include <boost/container/flat_set.hpp>
9#include <sdbusplus/asio/connection.hpp>
10#include <sdbusplus/asio/object_server.hpp>
11#include <sdbusplus/bus/match.hpp>
12
13#include <array>
14#include <filesystem>
15#include <fstream>
16#include <functional>
17#include <memory>
18#include <regex>
19#include <stdexcept>
20#include <string>
21#include <utility>
22#include <variant>
23#include <vector>
24
25// Copied from HwmonTempSensor and inspired by
26// https://gerrit.openbmc-project.xyz/c/openbmc/dbus-sensors/+/35476
27
28// The ExternalSensor is a sensor whose value is intended to be writable
29// by something external to the BMC, so that the host (or something else)
30// can write to it, perhaps by using an IPMI connection.
31
32// Unlike most other sensors, an external sensor does not correspond
33// to a hwmon file or other kernel/hardware interface,
34// so, after initialization, this module does not have much to do,
35// but it handles reinitialization and thresholds, similar to the others.
36
37// As there is no corresponding driver or hardware to support,
38// all configuration of this sensor comes from the JSON parameters:
39// MinValue, MaxValue, PowerState, Measure, Name
40
41// The purpose of "Measure" is to specify the physical characteristic
42// the external sensor is measuring, because with an external sensor
43// there is no other way to tell, and it will be used for the object path
44// here: /xyz/openbmc_project/sensors/<Measure>/<Name>
45
Ed Tanous8a57ec02020-10-09 12:46:52 -070046static constexpr bool debug = false;
Josh Lehan2a40e932020-09-02 11:48:14 -070047
48static const char* sensorType =
49 "xyz.openbmc_project.Configuration.ExternalSensor";
50
51void createSensors(
52 boost::asio::io_service& io, sdbusplus::asio::object_server& objectServer,
53 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>&
54 sensors,
55 std::shared_ptr<sdbusplus::asio::connection>& dbusConnection,
56 const std::shared_ptr<boost::container::flat_set<std::string>>&
57 sensorsChanged)
58{
59 auto getter = std::make_shared<GetSensorConfiguration>(
60 dbusConnection,
61 [&io, &objectServer, &sensors, &dbusConnection,
62 sensorsChanged](const ManagedObjectType& sensorConfigurations) {
63 bool firstScan = (sensorsChanged == nullptr);
64
65 for (const std::pair<sdbusplus::message::object_path, SensorData>&
66 sensor : sensorConfigurations)
67 {
68 const std::string& interfacePath = sensor.first.str;
69 const SensorData& sensorData = sensor.second;
70
71 auto sensorBase = sensorData.find(sensorType);
72 if (sensorBase == sensorData.end())
73 {
74 std::cerr << "Base configuration not found for "
75 << interfacePath << "\n";
76 continue;
77 }
78
79 const SensorBaseConfiguration& baseConfiguration = *sensorBase;
80 const SensorBaseConfigMap& baseConfigMap =
81 baseConfiguration.second;
82
83 double minValue;
84 double maxValue;
85
86 // MinValue and MinValue are mandatory numeric parameters
87 auto minFound = baseConfigMap.find("MinValue");
88 if (minFound == baseConfigMap.end())
89 {
90 std::cerr << "MinValue parameter not found for "
91 << interfacePath << "\n";
92 continue;
93 }
94 minValue =
95 std::visit(VariantToDoubleVisitor(), minFound->second);
96 if (!std::isfinite(minValue))
97 {
98 std::cerr << "MinValue parameter not parsed for "
99 << interfacePath << "\n";
100 continue;
101 }
102
103 auto maxFound = baseConfigMap.find("MaxValue");
104 if (maxFound == baseConfigMap.end())
105 {
106 std::cerr << "MaxValue parameter not found for "
107 << interfacePath << "\n";
108 continue;
109 }
110 maxValue =
111 std::visit(VariantToDoubleVisitor(), maxFound->second);
112 if (!std::isfinite(maxValue))
113 {
114 std::cerr << "MaxValue parameter not parsed for "
115 << interfacePath << "\n";
116 continue;
117 }
118
119 std::string sensorName;
120 std::string sensorMeasure;
121
122 // Name and Measure are mandatory string parameters
123 auto nameFound = baseConfigMap.find("Name");
124 if (nameFound == baseConfigMap.end())
125 {
126 std::cerr << "Name parameter not found for "
127 << interfacePath << "\n";
128 continue;
129 }
130 sensorName =
131 std::visit(VariantToStringVisitor(), nameFound->second);
132 if (sensorName.empty())
133 {
134 std::cerr << "Name parameter not parsed for "
135 << interfacePath << "\n";
136 continue;
137 }
138
139 auto measureFound = baseConfigMap.find("Units");
140 if (measureFound == baseConfigMap.end())
141 {
142 std::cerr << "Units parameter not found for "
143 << interfacePath << "\n";
144 continue;
145 }
146 sensorMeasure =
147 std::visit(VariantToStringVisitor(), measureFound->second);
148 if (sensorMeasure.empty())
149 {
150 std::cerr << "Measure parameter not parsed for "
151 << interfacePath << "\n";
152 continue;
153 }
154
155 // on rescans, only update sensors we were signaled by
156 auto findSensor = sensors.find(sensorName);
157 if (!firstScan && (findSensor != sensors.end()))
158 {
159 std::string suffixName = "/";
160 suffixName += findSensor->second->name;
161 bool found = false;
162 for (auto it = sensorsChanged->begin();
163 it != sensorsChanged->end(); it++)
164 {
165 std::string suffixIt = "/";
166 suffixIt += *it;
167 if (boost::ends_with(suffixIt, suffixName))
168 {
169 sensorsChanged->erase(it);
170 findSensor->second = nullptr;
171 found = true;
172 break;
173 }
174 }
175 if (!found)
176 {
177 continue;
178 }
179 }
180
181 std::vector<thresholds::Threshold> sensorThresholds;
182 if (!parseThresholdsFromConfig(sensorData, sensorThresholds))
183 {
184 std::cerr << "error populating thresholds for "
185 << sensorName << "\n";
186 }
187
188 auto findPowerOn = baseConfiguration.second.find("PowerState");
189 PowerState readState = PowerState::always;
190 if (findPowerOn != baseConfiguration.second.end())
191 {
192 std::string powerState = std::visit(
193 VariantToStringVisitor(), findPowerOn->second);
194 setReadState(powerState, readState);
195 }
196
197 auto& sensorEntry = sensors[sensorName];
198 sensorEntry = nullptr;
199
200 sensorEntry = std::make_shared<ExternalSensor>(
201 sensorType, objectServer, dbusConnection, sensorName,
202 sensorMeasure, std::move(sensorThresholds), interfacePath,
203 maxValue, minValue, readState);
204 }
205 });
206
207 getter->getConfiguration(std::vector<std::string>{sensorType});
208}
209
210int main()
211{
212 boost::asio::io_service io;
213 auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);
214 systemBus->request_name("xyz.openbmc_project.ExternalSensor");
215 sdbusplus::asio::object_server objectServer(systemBus);
216 boost::container::flat_map<std::string, std::shared_ptr<ExternalSensor>>
217 sensors;
218 std::vector<std::unique_ptr<sdbusplus::bus::match::match>> matches;
219 auto sensorsChanged =
220 std::make_shared<boost::container::flat_set<std::string>>();
221
222 io.post([&io, &objectServer, &sensors, &systemBus]() {
223 createSensors(io, objectServer, sensors, systemBus, nullptr);
224 });
225
226 boost::asio::deadline_timer filterTimer(io);
227 std::function<void(sdbusplus::message::message&)> eventHandler =
228 [&io, &objectServer, &sensors, &systemBus, &sensorsChanged,
229 &filterTimer](sdbusplus::message::message& message) {
230 if (message.is_method_error())
231 {
232 std::cerr << "callback method error\n";
233 return;
234 }
235 sensorsChanged->insert(message.get_path());
236 // this implicitly cancels the timer
237 filterTimer.expires_from_now(boost::posix_time::seconds(1));
238
239 filterTimer.async_wait([&io, &objectServer, &sensors, &systemBus,
240 &sensorsChanged](
241 const boost::system::error_code& ec) {
242 if (ec)
243 {
244 if (ec != boost::asio::error::operation_aborted)
245 {
246 std::cerr << "callback error: " << ec.message() << "\n";
247 }
248 return;
249 }
250 createSensors(io, objectServer, sensors, systemBus,
251 sensorsChanged);
252 });
253 };
254
255 auto match = std::make_unique<sdbusplus::bus::match::match>(
256 static_cast<sdbusplus::bus::bus&>(*systemBus),
257 "type='signal',member='PropertiesChanged',path_namespace='" +
258 std::string(inventoryPath) + "',arg0namespace='" + sensorType + "'",
259 eventHandler);
260 matches.emplace_back(std::move(match));
261
262 io.run();
263}