blob: 787ca3138fdb58a2db245c9eda7221064ab166e2 [file] [log] [blame]
James Feist7136a5a2018-07-19 09:52:05 -07001/*
2// Copyright (c) 2018 Intel Corporation
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
Chaul Lya552fe22024-11-15 10:20:28 +000016
Patrick Venture7e952d92020-10-05 15:58:52 -070017#include "dbusconfiguration.hpp"
James Feist7136a5a2018-07-19 09:52:05 -070018
Patrick Venture07716592018-10-14 11:46:40 -070019#include "conf.hpp"
Patrick Ventureef1f8862020-08-17 09:34:35 -070020#include "dbushelper.hpp"
21#include "dbusutil.hpp"
Ed Tanousf8b6e552025-06-27 13:27:50 -070022#include "ec/stepwise.hpp"
James Feist0c8223b2019-05-08 15:33:33 -070023#include "util.hpp"
Patrick Venture07716592018-10-14 11:46:40 -070024
Ed Tanousf8b6e552025-06-27 13:27:50 -070025#include <systemd/sd-bus.h>
26
27#include <boost/asio/error.hpp>
James Feist1fe08952019-05-07 09:17:16 -070028#include <boost/asio/steady_timer.hpp>
Patrick Venturea83a3ec2020-08-04 09:52:05 -070029#include <sdbusplus/bus.hpp>
30#include <sdbusplus/bus/match.hpp>
31#include <sdbusplus/exception.hpp>
Ed Tanousf8b6e552025-06-27 13:27:50 -070032#include <sdbusplus/message.hpp>
33#include <sdbusplus/message/native_types.hpp>
Patrick Venturea83a3ec2020-08-04 09:52:05 -070034
35#include <algorithm>
Ed Tanousf8b6e552025-06-27 13:27:50 -070036#include <array>
James Feist64f072a2018-08-10 16:39:24 -070037#include <chrono>
Ed Tanousf8b6e552025-06-27 13:27:50 -070038#include <cstdint>
39#include <format>
James Feist7136a5a2018-07-19 09:52:05 -070040#include <iostream>
Ed Tanousf8b6e552025-06-27 13:27:50 -070041#include <limits>
James Feist1fe08952019-05-07 09:17:16 -070042#include <list>
Ed Tanousf8b6e552025-06-27 13:27:50 -070043#include <map>
44#include <stdexcept>
45#include <string>
46#include <tuple>
James Feist7136a5a2018-07-19 09:52:05 -070047#include <unordered_map>
Ed Tanousf8b6e552025-06-27 13:27:50 -070048#include <utility>
James Feist1f802f52019-02-08 13:51:43 -080049#include <variant>
Ed Tanousf8b6e552025-06-27 13:27:50 -070050#include <vector>
James Feist7136a5a2018-07-19 09:52:05 -070051
Patrick Venturea0764872020-08-08 07:48:43 -070052namespace pid_control
53{
54
Patrick Venturee2ec0f62018-09-04 12:30:27 -070055constexpr const char* pidConfigurationInterface =
James Feist7136a5a2018-07-19 09:52:05 -070056 "xyz.openbmc_project.Configuration.Pid";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070057constexpr const char* objectManagerInterface =
James Feist7136a5a2018-07-19 09:52:05 -070058 "org.freedesktop.DBus.ObjectManager";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070059constexpr const char* pidZoneConfigurationInterface =
James Feist7136a5a2018-07-19 09:52:05 -070060 "xyz.openbmc_project.Configuration.Pid.Zone";
James Feist22c257a2018-08-31 14:07:12 -070061constexpr const char* stepwiseConfigurationInterface =
62 "xyz.openbmc_project.Configuration.Stepwise";
James Feistf0096a02019-02-21 11:25:22 -080063constexpr const char* thermalControlIface =
64 "xyz.openbmc_project.Control.ThermalMode";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070065constexpr const char* sensorInterface = "xyz.openbmc_project.Sensor.Value";
Patrick Venture0911bfe2020-08-10 12:51:40 -070066constexpr const char* defaultPwmInterface =
67 "xyz.openbmc_project.Control.FanPwm";
James Feist7136a5a2018-07-19 09:52:05 -070068
James Feist991ebd82020-07-21 11:14:52 -070069using Association = std::tuple<std::string, std::string, std::string>;
70using Associations = std::vector<Association>;
71
James Feist5ec20272019-07-10 11:59:57 -070072namespace thresholds
73{
74constexpr const char* warningInterface =
75 "xyz.openbmc_project.Sensor.Threshold.Warning";
76constexpr const char* criticalInterface =
77 "xyz.openbmc_project.Sensor.Threshold.Critical";
78const std::array<const char*, 4> types = {"CriticalLow", "CriticalHigh",
79 "WarningLow", "WarningHigh"};
80
81} // namespace thresholds
82
James Feist7136a5a2018-07-19 09:52:05 -070083namespace dbus_configuration
84{
Jason Lingf3b04fd2020-07-24 09:33:04 -070085using SensorInterfaceType = std::pair<std::string, std::string>;
86
87inline std::string getSensorNameFromPath(const std::string& dbusPath)
88{
Ed Tanousd2768c52025-06-26 11:42:57 -070089 return dbusPath.substr(dbusPath.find_last_of('/') + 1);
Jason Lingf3b04fd2020-07-24 09:33:04 -070090}
91
92inline std::string sensorNameToDbusName(const std::string& sensorName)
93{
94 std::string retString = sensorName;
95 std::replace(retString.begin(), retString.end(), ' ', '_');
96 return retString;
97}
James Feist5ec20272019-07-10 11:59:57 -070098
Patrick Williamsb228bc32022-07-22 19:26:56 -050099std::vector<std::string> getSelectedProfiles(sdbusplus::bus_t& bus)
James Feistf0096a02019-02-21 11:25:22 -0800100{
101 std::vector<std::string> ret;
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400102 auto mapper =
103 bus.new_method_call("xyz.openbmc_project.ObjectMapper",
104 "/xyz/openbmc_project/object_mapper",
105 "xyz.openbmc_project.ObjectMapper", "GetSubTree");
James Feistf0096a02019-02-21 11:25:22 -0800106 mapper.append("/", 0, std::array<const char*, 1>{thermalControlIface});
107 std::unordered_map<
108 std::string, std::unordered_map<std::string, std::vector<std::string>>>
109 respData;
110
111 try
112 {
113 auto resp = bus.call(mapper);
114 resp.read(respData);
115 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500116 catch (const sdbusplus::exception_t&)
James Feistf0096a02019-02-21 11:25:22 -0800117 {
118 // can't do anything without mapper call data
119 throw std::runtime_error("ObjectMapper Call Failure");
120 }
121 if (respData.empty())
122 {
123 // if the user has profiles but doesn't expose the interface to select
124 // one, just go ahead without using profiles
125 return ret;
126 }
127
128 // assumption is that we should only have a small handful of selected
129 // profiles at a time (probably only 1), so calling each individually should
130 // not incur a large cost
131 for (const auto& objectPair : respData)
132 {
133 const std::string& path = objectPair.first;
134 for (const auto& ownerPair : objectPair.second)
135 {
136 const std::string& busName = ownerPair.first;
137 auto getProfile =
138 bus.new_method_call(busName.c_str(), path.c_str(),
139 "org.freedesktop.DBus.Properties", "Get");
140 getProfile.append(thermalControlIface, "Current");
141 std::variant<std::string> variantResp;
142 try
143 {
144 auto resp = bus.call(getProfile);
145 resp.read(variantResp);
146 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500147 catch (const sdbusplus::exception_t&)
James Feistf0096a02019-02-21 11:25:22 -0800148 {
149 throw std::runtime_error("Failure getting profile");
150 }
151 std::string mode = std::get<std::string>(variantResp);
152 ret.emplace_back(std::move(mode));
153 }
154 }
Patrick Venture39199b42020-10-08 14:40:29 -0700155 if constexpr (pid_control::conf::DEBUG)
James Feistf0096a02019-02-21 11:25:22 -0800156 {
157 std::cout << "Profiles selected: ";
158 for (const auto& profile : ret)
159 {
160 std::cout << profile << " ";
161 }
162 std::cout << "\n";
163 }
164 return ret;
165}
166
James Feist991ebd82020-07-21 11:14:52 -0700167int eventHandler(sd_bus_message* m, void* context, sd_bus_error*)
James Feist7136a5a2018-07-19 09:52:05 -0700168{
James Feist991ebd82020-07-21 11:14:52 -0700169 if (context == nullptr || m == nullptr)
James Feist1fe08952019-05-07 09:17:16 -0700170 {
171 throw std::runtime_error("Invalid match");
172 }
James Feist991ebd82020-07-21 11:14:52 -0700173
174 // we skip associations because the mapper populates these, not the sensors
Josh Lehan10e46ef2023-02-01 18:25:58 -0800175 const std::array<const char*, 2> skipList = {
176 "xyz.openbmc_project.Association",
177 "xyz.openbmc_project.Association.Definitions"};
James Feist991ebd82020-07-21 11:14:52 -0700178
Patrick Williamsb228bc32022-07-22 19:26:56 -0500179 sdbusplus::message_t message(m);
James Feist991ebd82020-07-21 11:14:52 -0700180 if (std::string(message.get_member()) == "InterfacesAdded")
181 {
182 sdbusplus::message::object_path path;
183 std::unordered_map<
184 std::string,
185 std::unordered_map<std::string, std::variant<Associations, bool>>>
186 data;
187
188 message.read(path, data);
189
190 for (const char* skip : skipList)
191 {
192 auto find = data.find(skip);
193 if (find != data.end())
194 {
195 data.erase(find);
196 if (data.empty())
197 {
198 return 1;
199 }
200 }
201 }
Josh Lehan10e46ef2023-02-01 18:25:58 -0800202
203 if constexpr (pid_control::conf::DEBUG)
204 {
205 std::cout << "New config detected: " << path.str << std::endl;
206 for (auto& d : data)
207 {
208 std::cout << "\tdata is " << d.first << std::endl;
209 for (auto& second : d.second)
210 {
211 std::cout << "\t\tdata is " << second.first << std::endl;
212 }
213 }
214 }
James Feist991ebd82020-07-21 11:14:52 -0700215 }
216
James Feist1fe08952019-05-07 09:17:16 -0700217 boost::asio::steady_timer* timer =
218 static_cast<boost::asio::steady_timer*>(context);
219
220 // do a brief sleep as we tend to get a bunch of these events at
221 // once
222 timer->expires_after(std::chrono::seconds(2));
223 timer->async_wait([](const boost::system::error_code ec) {
224 if (ec == boost::asio::error::operation_aborted)
225 {
226 /* another timer started*/
227 return;
228 }
229
230 std::cout << "New configuration detected, reloading\n.";
Yong Li298a95c2020-04-07 15:11:02 +0800231 tryRestartControlLoops();
James Feist1fe08952019-05-07 09:17:16 -0700232 });
233
234 return 1;
235}
236
Patrick Williamsb228bc32022-07-22 19:26:56 -0500237void createMatches(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer)
James Feist1fe08952019-05-07 09:17:16 -0700238{
239 // this is a list because the matches can't be moved
Patrick Williamsb228bc32022-07-22 19:26:56 -0500240 static std::list<sdbusplus::bus::match_t> matches;
James Feist1fe08952019-05-07 09:17:16 -0700241
James Feist3987c8b2019-05-13 10:43:17 -0700242 const std::array<std::string, 4> interfaces = {
243 thermalControlIface, pidConfigurationInterface,
244 pidZoneConfigurationInterface, stepwiseConfigurationInterface};
James Feist1fe08952019-05-07 09:17:16 -0700245
246 // this list only needs to be created once
247 if (!matches.empty())
248 {
249 return;
250 }
251
252 // we restart when the configuration changes or there are new sensors
253 for (const auto& interface : interfaces)
254 {
255 matches.emplace_back(
256 bus,
257 "type='signal',member='PropertiesChanged',arg0namespace='" +
258 interface + "'",
259 eventHandler, &timer);
260 }
261 matches.emplace_back(
262 bus,
263 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
264 "sensors/'",
265 eventHandler, &timer);
Jinliang Wangc2a311b2023-04-26 18:36:56 +0000266 matches.emplace_back(bus,
267 "type='signal',member='InterfacesRemoved',arg0path='/"
268 "xyz/openbmc_project/sensors/'",
269 eventHandler, &timer);
James Feist1fe08952019-05-07 09:17:16 -0700270}
271
Jason Ling6fc301f2020-07-23 12:39:57 -0700272/**
273 * retrieve an attribute from the pid configuration map
274 * @param[in] base - the PID configuration map, keys are the attributes and
275 * value is the variant associated with that attribute.
276 * @param attributeName - the name of the attribute
277 * @return a variant holding the value associated with a key
278 * @throw runtime_error : attributeName is not in base
279 */
280inline DbusVariantType getPIDAttribute(
281 const std::unordered_map<std::string, DbusVariantType>& base,
282 const std::string& attributeName)
283{
284 auto search = base.find(attributeName);
285 if (search == base.end())
286 {
287 throw std::runtime_error("missing attribute " + attributeName);
288 }
289 return search->second;
290}
291
Harvey Wu239aa7d2022-11-18 08:43:34 +0800292inline void getCycleTimeSetting(
293 const std::unordered_map<std::string, DbusVariantType>& zone,
294 const int zoneIndex, const std::string& attributeName, uint64_t& value)
295{
296 auto findAttributeName = zone.find(attributeName);
297 if (findAttributeName != zone.end())
298 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400299 double tmpAttributeValue =
300 std::visit(VariantToDoubleVisitor(), zone.at(attributeName));
Harvey Wu239aa7d2022-11-18 08:43:34 +0800301 if (tmpAttributeValue >= 1.0)
302 {
303 value = static_cast<uint64_t>(tmpAttributeValue);
304 }
305 else
306 {
307 std::cerr << "Zone " << zoneIndex << ": " << attributeName
308 << " is invalid. Use default " << value << " ms\n";
309 }
310 }
311 else
312 {
313 std::cerr << "Zone " << zoneIndex << ": " << attributeName
314 << " cannot find setting. Use default " << value << " ms\n";
315 }
316}
317
James Feist5ec20272019-07-10 11:59:57 -0700318void populatePidInfo(
Patrick Williamscd1e78a2025-04-07 17:21:05 -0400319 sdbusplus::bus_t& bus,
James Feist5ec20272019-07-10 11:59:57 -0700320 const std::unordered_map<std::string, DbusVariantType>& base,
Patrick Venture1df9e872020-10-08 15:35:01 -0700321 conf::ControllerInfo& info, const std::string* thresholdProperty,
Patrick Venture73823182020-10-08 15:12:51 -0700322 const std::map<std::string, conf::SensorConfig>& sensorConfig)
James Feist5ec20272019-07-10 11:59:57 -0700323{
Jason Ling6fc301f2020-07-23 12:39:57 -0700324 info.type = std::get<std::string>(getPIDAttribute(base, "Class"));
James Feist5ec20272019-07-10 11:59:57 -0700325 if (info.type == "fan")
326 {
327 info.setpoint = 0;
328 }
329 else
330 {
Jason Ling6fc301f2020-07-23 12:39:57 -0700331 info.setpoint = std::visit(VariantToDoubleVisitor(),
332 getPIDAttribute(base, "SetPoint"));
James Feist5ec20272019-07-10 11:59:57 -0700333 }
334
ykchiu9fe3a3c2023-05-11 13:43:54 +0800335 int failsafepercent = 0;
336 auto findFailSafe = base.find("FailSafePercent");
337 if (findFailSafe != base.end())
338 {
339 failsafepercent = std::visit(VariantToDoubleVisitor(),
340 getPIDAttribute(base, "FailSafePercent"));
341 }
342 info.failSafePercent = failsafepercent;
343
James Feist5ec20272019-07-10 11:59:57 -0700344 if (thresholdProperty != nullptr)
345 {
346 std::string interface;
347 if (*thresholdProperty == "WarningHigh" ||
348 *thresholdProperty == "WarningLow")
349 {
350 interface = thresholds::warningInterface;
351 }
352 else
353 {
354 interface = thresholds::criticalInterface;
355 }
Josh Lehan31058fd2023-01-13 11:06:16 -0800356
357 // Although this checks only the first vector element for the
358 // named threshold, it is OK, because the SetPointOffset parser
359 // splits up the input into individual vectors, each with only a
360 // single element, if it detects that SetPointOffset is in use.
361 const std::string& path =
362 sensorConfig.at(info.inputs.front().name).readPath;
James Feist5ec20272019-07-10 11:59:57 -0700363
Patrick Williamscd1e78a2025-04-07 17:21:05 -0400364 DbusHelper helper(bus);
Patrick Venture9b936922020-08-10 11:28:39 -0700365 std::string service = helper.getService(interface, path);
James Feist5ec20272019-07-10 11:59:57 -0700366 double reading = 0;
367 try
368 {
Patrick Venture9b936922020-08-10 11:28:39 -0700369 helper.getProperty(service, path, interface, *thresholdProperty,
370 reading);
James Feist5ec20272019-07-10 11:59:57 -0700371 }
Patrick Williamsb228bc32022-07-22 19:26:56 -0500372 catch (const sdbusplus::exception_t& ex)
James Feist5ec20272019-07-10 11:59:57 -0700373 {
374 // unsupported threshold, leaving reading at 0
375 }
376
377 info.setpoint += reading;
378 }
379
380 info.pidInfo.ts = 1.0; // currently unused
Jason Ling6fc301f2020-07-23 12:39:57 -0700381 info.pidInfo.proportionalCoeff = std::visit(
382 VariantToDoubleVisitor(), getPIDAttribute(base, "PCoefficient"));
383 info.pidInfo.integralCoeff = std::visit(
384 VariantToDoubleVisitor(), getPIDAttribute(base, "ICoefficient"));
Josh Lehanc612c052022-12-12 09:56:47 -0800385 // DCoefficient is below, it is optional, same reason as in buildjson.cpp
Jason Ling6fc301f2020-07-23 12:39:57 -0700386 info.pidInfo.feedFwdOffset = std::visit(
387 VariantToDoubleVisitor(), getPIDAttribute(base, "FFOffCoefficient"));
388 info.pidInfo.feedFwdGain = std::visit(
389 VariantToDoubleVisitor(), getPIDAttribute(base, "FFGainCoefficient"));
390 info.pidInfo.integralLimit.max = std::visit(
391 VariantToDoubleVisitor(), getPIDAttribute(base, "ILimitMax"));
392 info.pidInfo.integralLimit.min = std::visit(
393 VariantToDoubleVisitor(), getPIDAttribute(base, "ILimitMin"));
394 info.pidInfo.outLim.max = std::visit(VariantToDoubleVisitor(),
395 getPIDAttribute(base, "OutLimitMax"));
396 info.pidInfo.outLim.min = std::visit(VariantToDoubleVisitor(),
397 getPIDAttribute(base, "OutLimitMin"));
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400398 info.pidInfo.slewNeg =
399 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewNeg"));
400 info.pidInfo.slewPos =
401 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewPos"));
Josh Lehanc612c052022-12-12 09:56:47 -0800402
Delphine CC Chiu97889632023-11-06 11:32:46 +0800403 bool checkHysterWithSetpt = false;
James Feist5ec20272019-07-10 11:59:57 -0700404 double negativeHysteresis = 0;
405 double positiveHysteresis = 0;
Josh Lehanc612c052022-12-12 09:56:47 -0800406 double derivativeCoeff = 0;
James Feist5ec20272019-07-10 11:59:57 -0700407
Delphine CC Chiu5d897e22024-06-04 13:33:22 +0800408 auto findCheckHysterFlag = base.find("CheckHysteresisWithSetpoint");
James Feist5ec20272019-07-10 11:59:57 -0700409 auto findNeg = base.find("NegativeHysteresis");
410 auto findPos = base.find("PositiveHysteresis");
Josh Lehanc612c052022-12-12 09:56:47 -0800411 auto findDerivative = base.find("DCoefficient");
James Feist5ec20272019-07-10 11:59:57 -0700412
Delphine CC Chiu97889632023-11-06 11:32:46 +0800413 if (findCheckHysterFlag != base.end())
414 {
415 checkHysterWithSetpt = std::get<bool>(findCheckHysterFlag->second);
416 }
James Feist5ec20272019-07-10 11:59:57 -0700417 if (findNeg != base.end())
418 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400419 negativeHysteresis =
420 std::visit(VariantToDoubleVisitor(), findNeg->second);
James Feist5ec20272019-07-10 11:59:57 -0700421 }
James Feist5ec20272019-07-10 11:59:57 -0700422 if (findPos != base.end())
423 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400424 positiveHysteresis =
425 std::visit(VariantToDoubleVisitor(), findPos->second);
James Feist5ec20272019-07-10 11:59:57 -0700426 }
Josh Lehanc612c052022-12-12 09:56:47 -0800427 if (findDerivative != base.end())
428 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400429 derivativeCoeff =
430 std::visit(VariantToDoubleVisitor(), findDerivative->second);
Josh Lehanc612c052022-12-12 09:56:47 -0800431 }
432
Delphine CC Chiu97889632023-11-06 11:32:46 +0800433 info.pidInfo.checkHysterWithSetpt = checkHysterWithSetpt;
James Feist5ec20272019-07-10 11:59:57 -0700434 info.pidInfo.negativeHysteresis = negativeHysteresis;
435 info.pidInfo.positiveHysteresis = positiveHysteresis;
Josh Lehanc612c052022-12-12 09:56:47 -0800436 info.pidInfo.derivativeCoeff = derivativeCoeff;
James Feist5ec20272019-07-10 11:59:57 -0700437}
438
Patrick Williamsb228bc32022-07-22 19:26:56 -0500439bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
Patrick Venture73823182020-10-08 15:12:51 -0700440 std::map<std::string, conf::SensorConfig>& sensorConfig,
441 std::map<int64_t, conf::PIDConf>& zoneConfig,
442 std::map<int64_t, conf::ZoneConfig>& zoneDetailsConfig)
James Feist1fe08952019-05-07 09:17:16 -0700443{
James Feist1fe08952019-05-07 09:17:16 -0700444 sensorConfig.clear();
445 zoneConfig.clear();
446 zoneDetailsConfig.clear();
447
448 createMatches(bus, timer);
449
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400450 auto mapper =
451 bus.new_method_call("xyz.openbmc_project.ObjectMapper",
452 "/xyz/openbmc_project/object_mapper",
453 "xyz.openbmc_project.ObjectMapper", "GetSubTree");
454 mapper.append(
455 "/", 0,
456 std::array<const char*, 6>{
457 objectManagerInterface, pidConfigurationInterface,
458 pidZoneConfigurationInterface, stepwiseConfigurationInterface,
459 sensorInterface, defaultPwmInterface});
James Feist7136a5a2018-07-19 09:52:05 -0700460 std::unordered_map<
461 std::string, std::unordered_map<std::string, std::vector<std::string>>>
462 respData;
James Feist22c257a2018-08-31 14:07:12 -0700463 try
464 {
465 auto resp = bus.call(mapper);
James Feist22c257a2018-08-31 14:07:12 -0700466 resp.read(respData);
467 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500468 catch (const sdbusplus::exception_t&)
James Feist22c257a2018-08-31 14:07:12 -0700469 {
470 // can't do anything without mapper call data
471 throw std::runtime_error("ObjectMapper Call Failure");
472 }
James Feist7136a5a2018-07-19 09:52:05 -0700473
James Feist7136a5a2018-07-19 09:52:05 -0700474 if (respData.empty())
475 {
James Feist22c257a2018-08-31 14:07:12 -0700476 // can't do anything without mapper call data
James Feist7136a5a2018-07-19 09:52:05 -0700477 throw std::runtime_error("No configuration data available from Mapper");
478 }
479 // create a map of pair of <has pid configuration, ObjectManager path>
480 std::unordered_map<std::string, std::pair<bool, std::string>> owners;
481 // and a map of <path, interface> for sensors
482 std::unordered_map<std::string, std::string> sensors;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700483 for (const auto& objectPair : respData)
James Feist7136a5a2018-07-19 09:52:05 -0700484 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700485 for (const auto& ownerPair : objectPair.second)
James Feist7136a5a2018-07-19 09:52:05 -0700486 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700487 auto& owner = owners[ownerPair.first];
488 for (const std::string& interface : ownerPair.second)
James Feist7136a5a2018-07-19 09:52:05 -0700489 {
James Feist7136a5a2018-07-19 09:52:05 -0700490 if (interface == objectManagerInterface)
491 {
492 owner.second = objectPair.first;
493 }
494 if (interface == pidConfigurationInterface ||
James Feist22c257a2018-08-31 14:07:12 -0700495 interface == pidZoneConfigurationInterface ||
496 interface == stepwiseConfigurationInterface)
James Feist7136a5a2018-07-19 09:52:05 -0700497 {
498 owner.first = true;
499 }
Patrick Venture0911bfe2020-08-10 12:51:40 -0700500 if (interface == sensorInterface ||
501 interface == defaultPwmInterface)
James Feist7136a5a2018-07-19 09:52:05 -0700502 {
503 // we're not interested in pwm sensors, just pwm control
504 if (interface == sensorInterface &&
505 objectPair.first.find("pwm") != std::string::npos)
506 {
507 continue;
508 }
509 sensors[objectPair.first] = interface;
510 }
511 }
512 }
513 }
514 ManagedObjectType configurations;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700515 for (const auto& owner : owners)
James Feist7136a5a2018-07-19 09:52:05 -0700516 {
517 // skip if no pid configuration (means probably a sensor)
518 if (!owner.second.first)
519 {
520 continue;
521 }
522 auto endpoint = bus.new_method_call(
523 owner.first.c_str(), owner.second.second.c_str(),
524 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
James Feist22c257a2018-08-31 14:07:12 -0700525 ManagedObjectType configuration;
526 try
James Feist7136a5a2018-07-19 09:52:05 -0700527 {
Manojkiran Eda7ca88872024-06-17 11:55:48 +0530528 auto response = bus.call(endpoint);
529 response.read(configuration);
James Feist22c257a2018-08-31 14:07:12 -0700530 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500531 catch (const sdbusplus::exception_t&)
James Feist22c257a2018-08-31 14:07:12 -0700532 {
533 // this shouldn't happen, probably means daemon crashed
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400534 throw std::runtime_error(
535 "Error getting managed objects from " + owner.first);
James Feist7136a5a2018-07-19 09:52:05 -0700536 }
James Feist22c257a2018-08-31 14:07:12 -0700537
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700538 for (auto& pathPair : configuration)
James Feist7136a5a2018-07-19 09:52:05 -0700539 {
540 if (pathPair.second.find(pidConfigurationInterface) !=
541 pathPair.second.end() ||
542 pathPair.second.find(pidZoneConfigurationInterface) !=
James Feist22c257a2018-08-31 14:07:12 -0700543 pathPair.second.end() ||
544 pathPair.second.find(stepwiseConfigurationInterface) !=
James Feist7136a5a2018-07-19 09:52:05 -0700545 pathPair.second.end())
546 {
547 configurations.emplace(pathPair);
548 }
James Feistf0096a02019-02-21 11:25:22 -0800549 }
550 }
551
552 // remove controllers from config that aren't in the current profile(s)
James Feist3987c8b2019-05-13 10:43:17 -0700553 std::vector<std::string> selectedProfiles = getSelectedProfiles(bus);
554 if (selectedProfiles.size())
James Feistf0096a02019-02-21 11:25:22 -0800555 {
James Feist3987c8b2019-05-13 10:43:17 -0700556 for (auto pathIt = configurations.begin();
557 pathIt != configurations.end();)
James Feistf0096a02019-02-21 11:25:22 -0800558 {
James Feist3987c8b2019-05-13 10:43:17 -0700559 for (auto confIt = pathIt->second.begin();
560 confIt != pathIt->second.end();)
James Feistf0096a02019-02-21 11:25:22 -0800561 {
James Feist3987c8b2019-05-13 10:43:17 -0700562 auto profilesFind = confIt->second.find("Profiles");
563 if (profilesFind == confIt->second.end())
James Feistf0096a02019-02-21 11:25:22 -0800564 {
James Feist3987c8b2019-05-13 10:43:17 -0700565 confIt++;
566 continue; // if no profiles selected, apply always
567 }
568 auto profiles =
569 std::get<std::vector<std::string>>(profilesFind->second);
570 if (profiles.empty())
571 {
572 confIt++;
573 continue;
574 }
575
576 bool found = false;
577 for (const std::string& profile : profiles)
578 {
579 if (std::find(selectedProfiles.begin(),
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400580 selectedProfiles.end(), profile) !=
581 selectedProfiles.end())
James Feist3987c8b2019-05-13 10:43:17 -0700582 {
583 found = true;
584 break;
585 }
586 }
587 if (found)
588 {
589 confIt++;
James Feistf0096a02019-02-21 11:25:22 -0800590 }
591 else
592 {
James Feist3987c8b2019-05-13 10:43:17 -0700593 confIt = pathIt->second.erase(confIt);
James Feistf0096a02019-02-21 11:25:22 -0800594 }
595 }
James Feist3987c8b2019-05-13 10:43:17 -0700596 if (pathIt->second.empty())
James Feistf0096a02019-02-21 11:25:22 -0800597 {
James Feist3987c8b2019-05-13 10:43:17 -0700598 pathIt = configurations.erase(pathIt);
James Feistf0096a02019-02-21 11:25:22 -0800599 }
James Feist3987c8b2019-05-13 10:43:17 -0700600 else
James Feistf0096a02019-02-21 11:25:22 -0800601 {
James Feist3987c8b2019-05-13 10:43:17 -0700602 pathIt++;
James Feistf0096a02019-02-21 11:25:22 -0800603 }
James Feist7136a5a2018-07-19 09:52:05 -0700604 }
605 }
James Feist8c3c51e2018-08-08 16:31:43 -0700606
Josh Lehan998fbe62020-09-20 21:21:05 -0700607 // On D-Bus, although not necessary,
608 // having the "zoneID" field can still be useful,
609 // as it is used for diagnostic messages,
610 // logging file names, and so on.
611 // Accept optional "ZoneIndex" parameter to explicitly specify.
612 // If not present, or not unique, auto-assign index,
613 // using 0-based numbering, ensuring uniqueness.
614 std::map<std::string, int64_t> foundZones;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700615 for (const auto& configuration : configurations)
James Feist7136a5a2018-07-19 09:52:05 -0700616 {
617 auto findZone =
618 configuration.second.find(pidZoneConfigurationInterface);
619 if (findZone != configuration.second.end())
620 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700621 const auto& zone = findZone->second;
James Feistffd418b2018-11-15 14:46:36 -0800622
James Feist1f802f52019-02-08 13:51:43 -0800623 const std::string& name = std::get<std::string>(zone.at("Name"));
Josh Lehan998fbe62020-09-20 21:21:05 -0700624
625 auto findZoneIndex = zone.find("ZoneIndex");
626 if (findZoneIndex == zone.end())
627 {
628 continue;
629 }
630
631 auto ptrZoneIndex = std::get_if<double>(&(findZoneIndex->second));
632 if (!ptrZoneIndex)
633 {
634 continue;
635 }
636
637 auto desiredIndex = static_cast<int64_t>(*ptrZoneIndex);
638 auto grantedIndex = setZoneIndex(name, foundZones, desiredIndex);
639 std::cout << "Zone " << name << " is at ZoneIndex " << grantedIndex
640 << "\n";
641 }
642 }
643
644 for (const auto& configuration : configurations)
645 {
646 auto findZone =
647 configuration.second.find(pidZoneConfigurationInterface);
648 if (findZone != configuration.second.end())
649 {
650 const auto& zone = findZone->second;
651
652 const std::string& name = std::get<std::string>(zone.at("Name"));
653
654 auto index = getZoneIndex(name, foundZones);
James Feist8c3c51e2018-08-08 16:31:43 -0700655
Patrick Venturec54fbd82018-10-30 19:40:05 -0700656 auto& details = zoneDetailsConfig[index];
Josh Lehan998fbe62020-09-20 21:21:05 -0700657
James Feist3484bed2019-02-25 13:28:18 -0800658 details.minThermalOutput = std::visit(VariantToDoubleVisitor(),
659 zone.at("MinThermalOutput"));
ykchiu9fe3a3c2023-05-11 13:43:54 +0800660
661 int failsafepercent = 0;
662 auto findFailSafe = zone.find("FailSafePercent");
663 if (findFailSafe != zone.end())
664 {
665 failsafepercent = std::visit(VariantToDoubleVisitor(),
666 zone.at("FailSafePercent"));
667 }
668 details.failsafePercent = failsafepercent;
Josh Lehan9f9a06a2022-12-14 10:39:45 -0800669
Harvey Wu239aa7d2022-11-18 08:43:34 +0800670 getCycleTimeSetting(zone, index, "CycleIntervalTimeMS",
671 details.cycleTime.cycleIntervalTimeMS);
672 getCycleTimeSetting(zone, index, "UpdateThermalsTimeMS",
673 details.cycleTime.updateThermalsTimeMS);
Delphine CC Chiu97889632023-11-06 11:32:46 +0800674
675 bool accumulateSetPoint = false;
676 auto findAccSetPoint = zone.find("AccumulateSetPoint");
677 if (findAccSetPoint != zone.end())
678 {
679 accumulateSetPoint = std::get<bool>(findAccSetPoint->second);
680 }
681 details.accumulateSetPoint = accumulateSetPoint;
James Feist7136a5a2018-07-19 09:52:05 -0700682 }
683 auto findBase = configuration.second.find(pidConfigurationInterface);
Jason Lingf3b04fd2020-07-24 09:33:04 -0700684 // loop through all the PID configurations and fill out a sensor config
James Feist22c257a2018-08-31 14:07:12 -0700685 if (findBase != configuration.second.end())
James Feist7136a5a2018-07-19 09:52:05 -0700686 {
James Feist22c257a2018-08-31 14:07:12 -0700687 const auto& base =
688 configuration.second.at(pidConfigurationInterface);
ykchiu7c6d35d2023-05-10 17:01:46 +0800689 const std::string pidName =
690 sensorNameToDbusName(std::get<std::string>(base.at("Name")));
Jason Lingf3b04fd2020-07-24 09:33:04 -0700691 const std::string pidClass =
692 std::get<std::string>(base.at("Class"));
James Feist22c257a2018-08-31 14:07:12 -0700693 const std::vector<std::string>& zones =
James Feist1f802f52019-02-08 13:51:43 -0800694 std::get<std::vector<std::string>>(base.at("Zones"));
James Feist22c257a2018-08-31 14:07:12 -0700695 for (const std::string& zone : zones)
James Feist7136a5a2018-07-19 09:52:05 -0700696 {
Josh Lehan998fbe62020-09-20 21:21:05 -0700697 auto index = getZoneIndex(zone, foundZones);
698
James Feistf81f2882019-02-26 11:26:36 -0800699 conf::PIDConf& conf = zoneConfig[index];
Jason Lingf3b04fd2020-07-24 09:33:04 -0700700 std::vector<std::string> inputSensorNames(
701 std::get<std::vector<std::string>>(base.at("Inputs")));
702 std::vector<std::string> outputSensorNames;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800703 std::vector<std::string> missingAcceptableSensorNames;
Chaul Lya552fe22024-11-15 10:20:28 +0000704 std::vector<std::string> archivedInputSensorNames;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800705
706 auto findMissingAcceptable = base.find("MissingIsAcceptable");
707 if (findMissingAcceptable != base.end())
708 {
709 missingAcceptableSensorNames =
710 std::get<std::vector<std::string>>(
711 findMissingAcceptable->second);
712 }
James Feist50fdfe32018-09-24 15:51:09 -0700713
Jason Lingf3b04fd2020-07-24 09:33:04 -0700714 // assumption: all fan pids must have at least one output
715 if (pidClass == "fan")
James Feist50fdfe32018-09-24 15:51:09 -0700716 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700717 outputSensorNames = std::get<std::vector<std::string>>(
718 getPIDAttribute(base, "Outputs"));
James Feist50fdfe32018-09-24 15:51:09 -0700719 }
James Feist1738e2a2019-02-04 15:57:03 -0800720
Alex.Song8f73ad72021-10-07 00:18:27 +0800721 bool unavailableAsFailed = true;
722 auto findUnavailableAsFailed =
723 base.find("InputUnavailableAsFailed");
724 if (findUnavailableAsFailed != base.end())
725 {
726 unavailableAsFailed =
727 std::get<bool>(findUnavailableAsFailed->second);
728 }
729
Jason Lingf3b04fd2020-07-24 09:33:04 -0700730 std::vector<SensorInterfaceType> inputSensorInterfaces;
731 std::vector<SensorInterfaceType> outputSensorInterfaces;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800732 std::vector<SensorInterfaceType>
733 missingAcceptableSensorInterfaces;
734
Jason Lingf3b04fd2020-07-24 09:33:04 -0700735 /* populate an interface list for different sensor direction
736 * types (input,output)
737 */
738 /* take the Inputs from the configuration and generate
739 * a list of dbus descriptors (path, interface).
740 * Mapping can be many-to-one since an element of Inputs can be
741 * a regex
742 */
743 for (const std::string& sensorName : inputSensorNames)
James Feist50fdfe32018-09-24 15:51:09 -0700744 {
Chaul Lya552fe22024-11-15 10:20:28 +0000745#ifndef HANDLE_MISSING_OBJECT_PATHS
Jason Lingf3b04fd2020-07-24 09:33:04 -0700746 findSensors(sensors, sensorNameToDbusName(sensorName),
747 inputSensorInterfaces);
Chaul Lya552fe22024-11-15 10:20:28 +0000748#else
749 std::vector<std::pair<std::string, std::string>>
750 sensorPathIfacePairs;
751 auto found =
752 findSensors(sensors, sensorNameToDbusName(sensorName),
753 sensorPathIfacePairs);
754 if (found)
755 {
756 inputSensorInterfaces.insert(
757 inputSensorInterfaces.end(),
758 sensorPathIfacePairs.begin(),
759 sensorPathIfacePairs.end());
760 }
761 else if (pidClass != "fan")
762 {
763 if (std::find(missingAcceptableSensorNames.begin(),
764 missingAcceptableSensorNames.end(),
765 sensorName) ==
766 missingAcceptableSensorNames.end())
767 {
768 std::cerr
769 << "Pid controller: Missing a missing-unacceptable sensor from D-Bus "
770 << sensorName << "\n";
771 std::string inputSensorName =
772 sensorNameToDbusName(sensorName);
773 auto& config = sensorConfig[inputSensorName];
774 archivedInputSensorNames.push_back(inputSensorName);
775 config.type = pidClass;
776 config.readPath =
777 getSensorPath(config.type, inputSensorName);
778 config.timeout = 0;
779 config.ignoreDbusMinMax = true;
780 config.unavailableAsFailed = unavailableAsFailed;
781 }
782 else
783 {
784 // When an input sensor is NOT on DBus, and it's in
785 // the MissingIsAcceptable list. Ignore it and
786 // continue with the next input sensor.
787 std::cout
788 << "Pid controller: Missing a missing-acceptable sensor from D-Bus "
789 << sensorName << "\n";
790 continue;
791 }
792 }
793#endif
Jason Lingf3b04fd2020-07-24 09:33:04 -0700794 }
795 for (const std::string& sensorName : outputSensorNames)
796 {
797 findSensors(sensors, sensorNameToDbusName(sensorName),
798 outputSensorInterfaces);
James Feist1738e2a2019-02-04 15:57:03 -0800799 }
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800800 for (const std::string& sensorName :
801 missingAcceptableSensorNames)
802 {
803 findSensors(sensors, sensorNameToDbusName(sensorName),
804 missingAcceptableSensorInterfaces);
805 }
James Feist50fdfe32018-09-24 15:51:09 -0700806
Jason Lingf3b04fd2020-07-24 09:33:04 -0700807 for (const SensorInterfaceType& inputSensorInterface :
808 inputSensorInterfaces)
James Feist1738e2a2019-02-04 15:57:03 -0800809 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700810 const std::string& dbusInterface =
811 inputSensorInterface.second;
812 const std::string& inputSensorPath =
813 inputSensorInterface.first;
Josh Lehanfb82a872020-09-20 21:48:22 -0700814
815 // Setting timeout to 0 is intentional, as D-Bus passive
816 // sensor updates are pushed in, not pulled by timer poll.
817 // Setting ignoreDbusMinMax is intentional, as this
818 // prevents normalization of values to [0.0, 1.0] range,
819 // which would mess up the PID loop math.
820 // All non-fan PID classes should be initialized this way.
821 // As for why a fan should not use this code path, see
822 // the ed1dafdf168def37c65bfb7a5efd18d9dbe04727 commit.
Josh Lehan23e22b92022-11-12 22:37:58 -0800823 if ((pidClass == "temp") || (pidClass == "margin") ||
824 (pidClass == "power") || (pidClass == "powersum"))
James Feist50fdfe32018-09-24 15:51:09 -0700825 {
Harvey.Wued1dafd2022-02-09 13:53:20 +0800826 std::string inputSensorName =
827 getSensorNameFromPath(inputSensorPath);
828 auto& config = sensorConfig[inputSensorName];
Chaul Lya552fe22024-11-15 10:20:28 +0000829 archivedInputSensorNames.push_back(inputSensorName);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800830 config.type = pidClass;
831 config.readPath = inputSensorInterface.first;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700832 config.timeout = 0;
833 config.ignoreDbusMinMax = true;
Alex.Song8f73ad72021-10-07 00:18:27 +0800834 config.unavailableAsFailed = unavailableAsFailed;
James Feist50fdfe32018-09-24 15:51:09 -0700835 }
Josh Lehanfb82a872020-09-20 21:48:22 -0700836
Jason Lingf3b04fd2020-07-24 09:33:04 -0700837 if (dbusInterface != sensorInterface)
James Feist50fdfe32018-09-24 15:51:09 -0700838 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700839 /* all expected inputs in the configuration are expected
840 * to be sensor interfaces
841 */
Ed Tanous7d6e2252025-06-27 10:45:25 -0700842 throw std::runtime_error(std::format(
843 "sensor at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
844 inputSensorPath, dbusInterface, sensorInterface));
James Feist50fdfe32018-09-24 15:51:09 -0700845 }
846 }
James Feist1738e2a2019-02-04 15:57:03 -0800847
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800848 // MissingIsAcceptable same postprocessing as Inputs
849 missingAcceptableSensorNames.clear();
850 for (const SensorInterfaceType&
851 missingAcceptableSensorInterface :
852 missingAcceptableSensorInterfaces)
853 {
854 const std::string& dbusInterface =
855 missingAcceptableSensorInterface.second;
856 const std::string& missingAcceptableSensorPath =
857 missingAcceptableSensorInterface.first;
858
859 std::string missingAcceptableSensorName =
860 getSensorNameFromPath(missingAcceptableSensorPath);
861 missingAcceptableSensorNames.push_back(
862 missingAcceptableSensorName);
863
864 if (dbusInterface != sensorInterface)
865 {
866 /* MissingIsAcceptable same error checking as Inputs
867 */
Ed Tanous7d6e2252025-06-27 10:45:25 -0700868 throw std::runtime_error(std::format(
869 "sensor at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
870 missingAcceptableSensorPath, dbusInterface,
871 sensorInterface));
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800872 }
873 }
874
Jason Lingf3b04fd2020-07-24 09:33:04 -0700875 /* fan pids need to pair up tach sensors with their pwm
876 * counterparts
877 */
878 if (pidClass == "fan")
879 {
880 /* If a PID is a fan there should be either
881 * (1) one output(pwm) per input(tach)
882 * OR
883 * (2) one putput(pwm) for all inputs(tach)
884 * everything else indicates a bad configuration.
885 */
886 bool singlePwm = false;
887 if (outputSensorInterfaces.size() == 1)
888 {
889 /* one pwm, set write paths for all fan sensors to it */
890 singlePwm = true;
891 }
892 else if (inputSensorInterfaces.size() ==
893 outputSensorInterfaces.size())
894 {
895 /* one to one mapping, each fan sensor gets its own pwm
896 * control */
897 singlePwm = false;
898 }
899 else
900 {
901 throw std::runtime_error(
902 "fan PID has invalid number of Outputs");
903 }
904 std::string fanSensorName;
905 std::string pwmPath;
906 std::string pwmInterface;
Harvey.Wued1dafd2022-02-09 13:53:20 +0800907 std::string pwmSensorName;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700908 if (singlePwm)
909 {
910 /* if just a single output(pwm) is provided then use
911 * that pwm control path for all the fan sensor write
912 * path configs
913 */
914 pwmPath = outputSensorInterfaces.at(0).first;
915 pwmInterface = outputSensorInterfaces.at(0).second;
916 }
917 for (uint32_t idx = 0; idx < inputSensorInterfaces.size();
918 idx++)
919 {
920 if (!singlePwm)
921 {
922 pwmPath = outputSensorInterfaces.at(idx).first;
923 pwmInterface =
924 outputSensorInterfaces.at(idx).second;
925 }
Patrick Venture0911bfe2020-08-10 12:51:40 -0700926 if (defaultPwmInterface != pwmInterface)
Jason Lingf3b04fd2020-07-24 09:33:04 -0700927 {
Ed Tanous7d6e2252025-06-27 10:45:25 -0700928 throw std::runtime_error(std::format(
929 "fan pwm control at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
930 pwmPath, pwmInterface, defaultPwmInterface));
Jason Lingf3b04fd2020-07-24 09:33:04 -0700931 }
932 const std::string& fanPath =
933 inputSensorInterfaces.at(idx).first;
934 fanSensorName = getSensorNameFromPath(fanPath);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800935 pwmSensorName = getSensorNameFromPath(pwmPath);
936 std::string fanPwmIndex = fanSensorName + pwmSensorName;
Chaul Lya552fe22024-11-15 10:20:28 +0000937 archivedInputSensorNames.push_back(fanPwmIndex);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800938 auto& fanConfig = sensorConfig[fanPwmIndex];
939 fanConfig.type = pidClass;
940 fanConfig.readPath = fanPath;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700941 fanConfig.writePath = pwmPath;
942 // todo: un-hardcode this if there are fans with
943 // different ranges
944 fanConfig.max = 255;
945 fanConfig.min = 0;
946 }
947 }
James Feist11d243d2019-06-24 16:18:40 -0700948 // if the sensors aren't available in the current state, don't
949 // add them to the configuration.
Chaul Lya552fe22024-11-15 10:20:28 +0000950 if (archivedInputSensorNames.empty())
James Feist11d243d2019-06-24 16:18:40 -0700951 {
952 continue;
953 }
954
James Feist5ec20272019-07-10 11:59:57 -0700955 std::string offsetType;
James Feist50fdfe32018-09-24 15:51:09 -0700956
James Feist5ec20272019-07-10 11:59:57 -0700957 // SetPointOffset is a threshold value to pull from the sensor
958 // to apply an offset. For upper thresholds this means the
959 // setpoint is usually negative.
960 auto findSetpointOffset = base.find("SetPointOffset");
961 if (findSetpointOffset != base.end())
James Feist22c257a2018-08-31 14:07:12 -0700962 {
James Feist5ec20272019-07-10 11:59:57 -0700963 offsetType =
964 std::get<std::string>(findSetpointOffset->second);
965 if (std::find(thresholds::types.begin(),
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400966 thresholds::types.end(), offsetType) ==
967 thresholds::types.end())
James Feist5ec20272019-07-10 11:59:57 -0700968 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400969 throw std::runtime_error(
970 "Unsupported type: " + offsetType);
James Feist5ec20272019-07-10 11:59:57 -0700971 }
972 }
973
Josh Lehan31058fd2023-01-13 11:06:16 -0800974 std::vector<double> inputTempToMargin;
975
976 auto findTempToMargin = base.find("TempToMargin");
977 if (findTempToMargin != base.end())
978 {
979 inputTempToMargin =
980 std::get<std::vector<double>>(findTempToMargin->second);
981 }
982
983 std::vector<pid_control::conf::SensorInput> sensorInputs =
Chaul Lya552fe22024-11-15 10:20:28 +0000984 spliceInputs(archivedInputSensorNames, inputTempToMargin,
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800985 missingAcceptableSensorNames);
Josh Lehan31058fd2023-01-13 11:06:16 -0800986
James Feist5ec20272019-07-10 11:59:57 -0700987 if (offsetType.empty())
988 {
ykchiu7c6d35d2023-05-10 17:01:46 +0800989 conf::ControllerInfo& info = conf[pidName];
Josh Lehan31058fd2023-01-13 11:06:16 -0800990 info.inputs = std::move(sensorInputs);
Patrick Venture73823182020-10-08 15:12:51 -0700991 populatePidInfo(bus, base, info, nullptr, sensorConfig);
James Feist22c257a2018-08-31 14:07:12 -0700992 }
993 else
994 {
James Feist5ec20272019-07-10 11:59:57 -0700995 // we have to split up the inputs, as in practice t-control
996 // values will differ, making setpoints differ
Josh Lehan31058fd2023-01-13 11:06:16 -0800997 for (const pid_control::conf::SensorInput& input :
998 sensorInputs)
James Feist5ec20272019-07-10 11:59:57 -0700999 {
Josh Lehan31058fd2023-01-13 11:06:16 -08001000 conf::ControllerInfo& info = conf[input.name];
James Feist5ec20272019-07-10 11:59:57 -07001001 info.inputs.emplace_back(input);
Patrick Venture73823182020-10-08 15:12:51 -07001002 populatePidInfo(bus, base, info, &offsetType,
1003 sensorConfig);
James Feist5ec20272019-07-10 11:59:57 -07001004 }
James Feist22c257a2018-08-31 14:07:12 -07001005 }
James Feist22c257a2018-08-31 14:07:12 -07001006 }
1007 }
1008 auto findStepwise =
1009 configuration.second.find(stepwiseConfigurationInterface);
1010 if (findStepwise != configuration.second.end())
1011 {
1012 const auto& base = findStepwise->second;
ykchiu7c6d35d2023-05-10 17:01:46 +08001013 const std::string pidName =
1014 sensorNameToDbusName(std::get<std::string>(base.at("Name")));
James Feist22c257a2018-08-31 14:07:12 -07001015 const std::vector<std::string>& zones =
James Feist1f802f52019-02-08 13:51:43 -08001016 std::get<std::vector<std::string>>(base.at("Zones"));
James Feist22c257a2018-08-31 14:07:12 -07001017 for (const std::string& zone : zones)
1018 {
Josh Lehan998fbe62020-09-20 21:21:05 -07001019 auto index = getZoneIndex(zone, foundZones);
1020
James Feistf81f2882019-02-26 11:26:36 -08001021 conf::PIDConf& conf = zoneConfig[index];
James Feist50fdfe32018-09-24 15:51:09 -07001022
1023 std::vector<std::string> inputs;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001024 std::vector<std::string> missingAcceptableSensors;
1025 std::vector<std::string> missingAcceptableSensorNames;
James Feist50fdfe32018-09-24 15:51:09 -07001026 std::vector<std::string> sensorNames =
James Feist1f802f52019-02-08 13:51:43 -08001027 std::get<std::vector<std::string>>(base.at("Inputs"));
James Feist50fdfe32018-09-24 15:51:09 -07001028
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001029 auto findMissingAcceptable = base.find("MissingIsAcceptable");
1030 if (findMissingAcceptable != base.end())
1031 {
1032 missingAcceptableSensorNames =
1033 std::get<std::vector<std::string>>(
1034 findMissingAcceptable->second);
1035 }
1036
Alex.Song8f73ad72021-10-07 00:18:27 +08001037 bool unavailableAsFailed = true;
1038 auto findUnavailableAsFailed =
1039 base.find("InputUnavailableAsFailed");
1040 if (findUnavailableAsFailed != base.end())
1041 {
1042 unavailableAsFailed =
1043 std::get<bool>(findUnavailableAsFailed->second);
1044 }
1045
James Feist1738e2a2019-02-04 15:57:03 -08001046 bool sensorFound = false;
James Feist50fdfe32018-09-24 15:51:09 -07001047 for (const std::string& sensorName : sensorNames)
1048 {
James Feist1738e2a2019-02-04 15:57:03 -08001049 std::vector<std::pair<std::string, std::string>>
1050 sensorPathIfacePairs;
Jason Lingf3b04fd2020-07-24 09:33:04 -07001051 if (!findSensors(sensors, sensorNameToDbusName(sensorName),
1052 sensorPathIfacePairs))
James Feist50fdfe32018-09-24 15:51:09 -07001053 {
Chaul Lya552fe22024-11-15 10:20:28 +00001054#ifndef HANDLE_MISSING_OBJECT_PATHS
James Feist50fdfe32018-09-24 15:51:09 -07001055 break;
Chaul Lya552fe22024-11-15 10:20:28 +00001056#else
1057 if (std::find(missingAcceptableSensorNames.begin(),
1058 missingAcceptableSensorNames.end(),
1059 sensorName) ==
1060 missingAcceptableSensorNames.end())
1061 {
1062 // When an input sensor is NOT on DBus, and it's NOT
1063 // in the MissingIsAcceptable list. Build it as a
1064 // failed sensor with default information (temp
1065 // sensor path, temp type, ...)
1066 std::cerr
1067 << "Stepwise controller: Missing a missing-unacceptable sensor from D-Bus "
1068 << sensorName << "\n";
1069 std::string shortName =
1070 sensorNameToDbusName(sensorName);
1071
1072 inputs.push_back(shortName);
1073 auto& config = sensorConfig[shortName];
1074 config.type = "temp";
1075 config.readPath =
1076 getSensorPath(config.type, shortName);
1077 config.ignoreDbusMinMax = true;
1078 config.unavailableAsFailed = unavailableAsFailed;
1079 // todo: maybe un-hardcode this if we run into
1080 // slower timeouts with sensors
1081
1082 config.timeout = 0;
1083 sensorFound = true;
1084 }
1085 else
1086 {
1087 // When an input sensor is NOT on DBus, and it's in
1088 // the MissingIsAcceptable list. Ignore it and
1089 // continue with the next input sensor.
1090 std::cout
1091 << "Stepwise controller: Missing a missing-acceptable sensor from D-Bus "
1092 << sensorName << "\n";
1093 continue;
1094 }
1095#endif
James Feist50fdfe32018-09-24 15:51:09 -07001096 }
Chaul Lya552fe22024-11-15 10:20:28 +00001097 else
James Feist1738e2a2019-02-04 15:57:03 -08001098 {
Chaul Lya552fe22024-11-15 10:20:28 +00001099 for (const auto& sensorPathIfacePair :
1100 sensorPathIfacePairs)
1101 {
1102 std::string shortName = getSensorNameFromPath(
1103 sensorPathIfacePair.first);
James Feist50fdfe32018-09-24 15:51:09 -07001104
Chaul Lya552fe22024-11-15 10:20:28 +00001105 inputs.push_back(shortName);
1106 auto& config = sensorConfig[shortName];
1107 config.readPath = sensorPathIfacePair.first;
1108 config.type = "temp";
1109 config.ignoreDbusMinMax = true;
1110 config.unavailableAsFailed = unavailableAsFailed;
1111 // todo: maybe un-hardcode this if we run into
1112 // slower timeouts with sensors
James Feist1738e2a2019-02-04 15:57:03 -08001113
Chaul Lya552fe22024-11-15 10:20:28 +00001114 config.timeout = 0;
1115 sensorFound = true;
1116 }
James Feist1738e2a2019-02-04 15:57:03 -08001117 }
James Feist50fdfe32018-09-24 15:51:09 -07001118 }
1119 if (!sensorFound)
1120 {
1121 continue;
1122 }
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001123
1124 // MissingIsAcceptable same postprocessing as Inputs
1125 for (const std::string& missingAcceptableSensorName :
1126 missingAcceptableSensorNames)
1127 {
1128 std::vector<std::pair<std::string, std::string>>
1129 sensorPathIfacePairs;
1130 if (!findSensors(
1131 sensors,
1132 sensorNameToDbusName(missingAcceptableSensorName),
1133 sensorPathIfacePairs))
1134 {
Chaul Lya552fe22024-11-15 10:20:28 +00001135#ifndef HANDLE_MISSING_OBJECT_PATHS
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001136 break;
Chaul Lya552fe22024-11-15 10:20:28 +00001137#else
1138 // When a sensor in the MissingIsAcceptable list is NOT
1139 // on DBus and it still reaches here, which contradicts
1140 // to what we did in the Input sensor building step.
1141 // Continue.
1142 continue;
1143#endif
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001144 }
1145
1146 for (const auto& sensorPathIfacePair : sensorPathIfacePairs)
1147 {
1148 std::string shortName =
1149 getSensorNameFromPath(sensorPathIfacePair.first);
1150
1151 missingAcceptableSensors.push_back(shortName);
1152 }
1153 }
1154
ykchiu7c6d35d2023-05-10 17:01:46 +08001155 conf::ControllerInfo& info = conf[pidName];
Josh Lehan31058fd2023-01-13 11:06:16 -08001156
1157 std::vector<double> inputTempToMargin;
1158
1159 auto findTempToMargin = base.find("TempToMargin");
1160 if (findTempToMargin != base.end())
1161 {
1162 inputTempToMargin =
1163 std::get<std::vector<double>>(findTempToMargin->second);
1164 }
1165
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001166 info.inputs = spliceInputs(inputs, inputTempToMargin,
1167 missingAcceptableSensors);
James Feist50fdfe32018-09-24 15:51:09 -07001168
James Feist22c257a2018-08-31 14:07:12 -07001169 info.type = "stepwise";
1170 info.stepwiseInfo.ts = 1.0; // currently unused
James Feist3dfaafd2018-09-20 15:46:58 -07001171 info.stepwiseInfo.positiveHysteresis = 0.0;
1172 info.stepwiseInfo.negativeHysteresis = 0.0;
James Feist608304d2019-02-25 10:01:42 -08001173 std::string subtype = std::get<std::string>(base.at("Class"));
1174
1175 info.stepwiseInfo.isCeiling = (subtype == "Ceiling");
James Feist3dfaafd2018-09-20 15:46:58 -07001176 auto findPosHyst = base.find("PositiveHysteresis");
1177 auto findNegHyst = base.find("NegativeHysteresis");
1178 if (findPosHyst != base.end())
1179 {
James Feist1f802f52019-02-08 13:51:43 -08001180 info.stepwiseInfo.positiveHysteresis = std::visit(
James Feist208abce2018-12-06 09:59:10 -08001181 VariantToDoubleVisitor(), findPosHyst->second);
James Feist3dfaafd2018-09-20 15:46:58 -07001182 }
1183 if (findNegHyst != base.end())
1184 {
James Feist5782ab82019-04-02 08:38:48 -07001185 info.stepwiseInfo.negativeHysteresis = std::visit(
James Feist208abce2018-12-06 09:59:10 -08001186 VariantToDoubleVisitor(), findNegHyst->second);
James Feist3dfaafd2018-09-20 15:46:58 -07001187 }
James Feist22c257a2018-08-31 14:07:12 -07001188 std::vector<double> readings =
James Feist1f802f52019-02-08 13:51:43 -08001189 std::get<std::vector<double>>(base.at("Reading"));
James Feist22c257a2018-08-31 14:07:12 -07001190 if (readings.size() > ec::maxStepwisePoints)
1191 {
1192 throw std::invalid_argument("Too many stepwise points.");
1193 }
1194 if (readings.empty())
1195 {
1196 throw std::invalid_argument(
1197 "Must have one stepwise point.");
1198 }
1199 std::copy(readings.begin(), readings.end(),
1200 info.stepwiseInfo.reading);
1201 if (readings.size() < ec::maxStepwisePoints)
1202 {
1203 info.stepwiseInfo.reading[readings.size()] =
Patrick Venture5f59c0f2018-11-11 12:55:14 -08001204 std::numeric_limits<double>::quiet_NaN();
James Feist22c257a2018-08-31 14:07:12 -07001205 }
1206 std::vector<double> outputs =
James Feist1f802f52019-02-08 13:51:43 -08001207 std::get<std::vector<double>>(base.at("Output"));
James Feist22c257a2018-08-31 14:07:12 -07001208 if (readings.size() != outputs.size())
1209 {
1210 throw std::invalid_argument(
1211 "Outputs size must match readings");
1212 }
1213 std::copy(outputs.begin(), outputs.end(),
1214 info.stepwiseInfo.output);
1215 if (outputs.size() < ec::maxStepwisePoints)
1216 {
1217 info.stepwiseInfo.output[outputs.size()] =
Patrick Venture5f59c0f2018-11-11 12:55:14 -08001218 std::numeric_limits<double>::quiet_NaN();
James Feist22c257a2018-08-31 14:07:12 -07001219 }
James Feist7136a5a2018-07-19 09:52:05 -07001220 }
1221 }
1222 }
Patrick Venture39199b42020-10-08 14:40:29 -07001223 if constexpr (pid_control::conf::DEBUG)
James Feist7136a5a2018-07-19 09:52:05 -07001224 {
Patrick Venture39199b42020-10-08 14:40:29 -07001225 debugPrint(sensorConfig, zoneConfig, zoneDetailsConfig);
James Feist7136a5a2018-07-19 09:52:05 -07001226 }
James Feistc959c422018-11-01 12:33:40 -07001227 if (zoneConfig.empty() || zoneDetailsConfig.empty())
James Feist50fdfe32018-09-24 15:51:09 -07001228 {
James Feist1fe08952019-05-07 09:17:16 -07001229 std::cerr
1230 << "No fan zones, application pausing until new configuration\n";
1231 return false;
James Feist50fdfe32018-09-24 15:51:09 -07001232 }
James Feist1fe08952019-05-07 09:17:16 -07001233 return true;
James Feist7136a5a2018-07-19 09:52:05 -07001234}
Patrick Venturea0764872020-08-08 07:48:43 -07001235
James Feist7136a5a2018-07-19 09:52:05 -07001236} // namespace dbus_configuration
Patrick Venturea0764872020-08-08 07:48:43 -07001237} // namespace pid_control