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