blob: 8dcd69b7929cde344c51ba0710e2b53ab29e322a [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*/
Pete O_o765a6d82025-07-23 21:44:14 -070016#include "config.h"
Chaul Lya552fe22024-11-15 10:20:28 +000017
Patrick Venture7e952d92020-10-05 15:58:52 -070018#include "dbusconfiguration.hpp"
James Feist7136a5a2018-07-19 09:52:05 -070019
Patrick Venture07716592018-10-14 11:46:40 -070020#include "conf.hpp"
Patrick Ventureef1f8862020-08-17 09:34:35 -070021#include "dbushelper.hpp"
22#include "dbusutil.hpp"
Ed Tanousf8b6e552025-06-27 13:27:50 -070023#include "ec/stepwise.hpp"
James Feist0c8223b2019-05-08 15:33:33 -070024#include "util.hpp"
Patrick Venture07716592018-10-14 11:46:40 -070025
Ed Tanousf8b6e552025-06-27 13:27:50 -070026#include <systemd/sd-bus.h>
27
28#include <boost/asio/error.hpp>
James Feist1fe08952019-05-07 09:17:16 -070029#include <boost/asio/steady_timer.hpp>
Patrick Venturea83a3ec2020-08-04 09:52:05 -070030#include <sdbusplus/bus.hpp>
31#include <sdbusplus/bus/match.hpp>
32#include <sdbusplus/exception.hpp>
Ed Tanousf8b6e552025-06-27 13:27:50 -070033#include <sdbusplus/message.hpp>
34#include <sdbusplus/message/native_types.hpp>
Patrick Venturea83a3ec2020-08-04 09:52:05 -070035
36#include <algorithm>
Ed Tanousf8b6e552025-06-27 13:27:50 -070037#include <array>
James Feist64f072a2018-08-10 16:39:24 -070038#include <chrono>
Ed Tanousf8b6e552025-06-27 13:27:50 -070039#include <cstdint>
40#include <format>
James Feist7136a5a2018-07-19 09:52:05 -070041#include <iostream>
Ed Tanousf8b6e552025-06-27 13:27:50 -070042#include <limits>
James Feist1fe08952019-05-07 09:17:16 -070043#include <list>
Ed Tanousf8b6e552025-06-27 13:27:50 -070044#include <map>
45#include <stdexcept>
46#include <string>
47#include <tuple>
James Feist7136a5a2018-07-19 09:52:05 -070048#include <unordered_map>
Ed Tanousf8b6e552025-06-27 13:27:50 -070049#include <utility>
James Feist1f802f52019-02-08 13:51:43 -080050#include <variant>
Ed Tanousf8b6e552025-06-27 13:27:50 -070051#include <vector>
James Feist7136a5a2018-07-19 09:52:05 -070052
Patrick Venturea0764872020-08-08 07:48:43 -070053namespace pid_control
54{
55
Patrick Venturee2ec0f62018-09-04 12:30:27 -070056constexpr const char* pidConfigurationInterface =
James Feist7136a5a2018-07-19 09:52:05 -070057 "xyz.openbmc_project.Configuration.Pid";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070058constexpr const char* objectManagerInterface =
James Feist7136a5a2018-07-19 09:52:05 -070059 "org.freedesktop.DBus.ObjectManager";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070060constexpr const char* pidZoneConfigurationInterface =
James Feist7136a5a2018-07-19 09:52:05 -070061 "xyz.openbmc_project.Configuration.Pid.Zone";
James Feist22c257a2018-08-31 14:07:12 -070062constexpr const char* stepwiseConfigurationInterface =
63 "xyz.openbmc_project.Configuration.Stepwise";
James Feistf0096a02019-02-21 11:25:22 -080064constexpr const char* thermalControlIface =
65 "xyz.openbmc_project.Control.ThermalMode";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070066constexpr const char* sensorInterface = "xyz.openbmc_project.Sensor.Value";
Patrick Venture0911bfe2020-08-10 12:51:40 -070067constexpr const char* defaultPwmInterface =
68 "xyz.openbmc_project.Control.FanPwm";
James Feist7136a5a2018-07-19 09:52:05 -070069
James Feist991ebd82020-07-21 11:14:52 -070070using Association = std::tuple<std::string, std::string, std::string>;
71using Associations = std::vector<Association>;
72
James Feist5ec20272019-07-10 11:59:57 -070073namespace thresholds
74{
75constexpr const char* warningInterface =
76 "xyz.openbmc_project.Sensor.Threshold.Warning";
77constexpr const char* criticalInterface =
78 "xyz.openbmc_project.Sensor.Threshold.Critical";
79const std::array<const char*, 4> types = {"CriticalLow", "CriticalHigh",
80 "WarningLow", "WarningHigh"};
81
82} // namespace thresholds
83
James Feist7136a5a2018-07-19 09:52:05 -070084namespace dbus_configuration
85{
Jason Lingf3b04fd2020-07-24 09:33:04 -070086using SensorInterfaceType = std::pair<std::string, std::string>;
87
88inline std::string getSensorNameFromPath(const std::string& dbusPath)
89{
Ed Tanousd2768c52025-06-26 11:42:57 -070090 return dbusPath.substr(dbusPath.find_last_of('/') + 1);
Jason Lingf3b04fd2020-07-24 09:33:04 -070091}
92
93inline std::string sensorNameToDbusName(const std::string& sensorName)
94{
95 std::string retString = sensorName;
96 std::replace(retString.begin(), retString.end(), ' ', '_');
97 return retString;
98}
James Feist5ec20272019-07-10 11:59:57 -070099
Patrick Williamsb228bc32022-07-22 19:26:56 -0500100std::vector<std::string> getSelectedProfiles(sdbusplus::bus_t& bus)
James Feistf0096a02019-02-21 11:25:22 -0800101{
102 std::vector<std::string> ret;
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400103 auto mapper =
104 bus.new_method_call("xyz.openbmc_project.ObjectMapper",
105 "/xyz/openbmc_project/object_mapper",
106 "xyz.openbmc_project.ObjectMapper", "GetSubTree");
James Feistf0096a02019-02-21 11:25:22 -0800107 mapper.append("/", 0, std::array<const char*, 1>{thermalControlIface});
108 std::unordered_map<
109 std::string, std::unordered_map<std::string, std::vector<std::string>>>
110 respData;
111
112 try
113 {
114 auto resp = bus.call(mapper);
115 resp.read(respData);
116 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500117 catch (const sdbusplus::exception_t&)
James Feistf0096a02019-02-21 11:25:22 -0800118 {
119 // can't do anything without mapper call data
120 throw std::runtime_error("ObjectMapper Call Failure");
121 }
122 if (respData.empty())
123 {
124 // if the user has profiles but doesn't expose the interface to select
125 // one, just go ahead without using profiles
126 return ret;
127 }
128
129 // assumption is that we should only have a small handful of selected
130 // profiles at a time (probably only 1), so calling each individually should
131 // not incur a large cost
132 for (const auto& objectPair : respData)
133 {
134 const std::string& path = objectPair.first;
135 for (const auto& ownerPair : objectPair.second)
136 {
137 const std::string& busName = ownerPair.first;
138 auto getProfile =
139 bus.new_method_call(busName.c_str(), path.c_str(),
140 "org.freedesktop.DBus.Properties", "Get");
141 getProfile.append(thermalControlIface, "Current");
142 std::variant<std::string> variantResp;
143 try
144 {
145 auto resp = bus.call(getProfile);
146 resp.read(variantResp);
147 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500148 catch (const sdbusplus::exception_t&)
James Feistf0096a02019-02-21 11:25:22 -0800149 {
150 throw std::runtime_error("Failure getting profile");
151 }
152 std::string mode = std::get<std::string>(variantResp);
153 ret.emplace_back(std::move(mode));
154 }
155 }
Patrick Venture39199b42020-10-08 14:40:29 -0700156 if constexpr (pid_control::conf::DEBUG)
James Feistf0096a02019-02-21 11:25:22 -0800157 {
158 std::cout << "Profiles selected: ";
159 for (const auto& profile : ret)
160 {
161 std::cout << profile << " ";
162 }
163 std::cout << "\n";
164 }
165 return ret;
166}
167
James Feist991ebd82020-07-21 11:14:52 -0700168int eventHandler(sd_bus_message* m, void* context, sd_bus_error*)
James Feist7136a5a2018-07-19 09:52:05 -0700169{
James Feist991ebd82020-07-21 11:14:52 -0700170 if (context == nullptr || m == nullptr)
James Feist1fe08952019-05-07 09:17:16 -0700171 {
172 throw std::runtime_error("Invalid match");
173 }
James Feist991ebd82020-07-21 11:14:52 -0700174
175 // we skip associations because the mapper populates these, not the sensors
Josh Lehan10e46ef2023-02-01 18:25:58 -0800176 const std::array<const char*, 2> skipList = {
177 "xyz.openbmc_project.Association",
178 "xyz.openbmc_project.Association.Definitions"};
James Feist991ebd82020-07-21 11:14:52 -0700179
Patrick Williamsb228bc32022-07-22 19:26:56 -0500180 sdbusplus::message_t message(m);
James Feist991ebd82020-07-21 11:14:52 -0700181 if (std::string(message.get_member()) == "InterfacesAdded")
182 {
183 sdbusplus::message::object_path path;
184 std::unordered_map<
185 std::string,
186 std::unordered_map<std::string, std::variant<Associations, bool>>>
187 data;
188
189 message.read(path, data);
190
191 for (const char* skip : skipList)
192 {
193 auto find = data.find(skip);
194 if (find != data.end())
195 {
196 data.erase(find);
197 if (data.empty())
198 {
199 return 1;
200 }
201 }
202 }
Josh Lehan10e46ef2023-02-01 18:25:58 -0800203
204 if constexpr (pid_control::conf::DEBUG)
205 {
206 std::cout << "New config detected: " << path.str << std::endl;
207 for (auto& d : data)
208 {
209 std::cout << "\tdata is " << d.first << std::endl;
210 for (auto& second : d.second)
211 {
212 std::cout << "\t\tdata is " << second.first << std::endl;
213 }
214 }
215 }
James Feist991ebd82020-07-21 11:14:52 -0700216 }
217
James Feist1fe08952019-05-07 09:17:16 -0700218 boost::asio::steady_timer* timer =
219 static_cast<boost::asio::steady_timer*>(context);
220
221 // do a brief sleep as we tend to get a bunch of these events at
222 // once
223 timer->expires_after(std::chrono::seconds(2));
224 timer->async_wait([](const boost::system::error_code ec) {
225 if (ec == boost::asio::error::operation_aborted)
226 {
227 /* another timer started*/
228 return;
229 }
230
231 std::cout << "New configuration detected, reloading\n.";
Yong Li298a95c2020-04-07 15:11:02 +0800232 tryRestartControlLoops();
James Feist1fe08952019-05-07 09:17:16 -0700233 });
234
235 return 1;
236}
237
Patrick Williamsb228bc32022-07-22 19:26:56 -0500238void createMatches(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer)
James Feist1fe08952019-05-07 09:17:16 -0700239{
240 // this is a list because the matches can't be moved
Patrick Williamsb228bc32022-07-22 19:26:56 -0500241 static std::list<sdbusplus::bus::match_t> matches;
James Feist1fe08952019-05-07 09:17:16 -0700242
James Feist3987c8b2019-05-13 10:43:17 -0700243 const std::array<std::string, 4> interfaces = {
244 thermalControlIface, pidConfigurationInterface,
245 pidZoneConfigurationInterface, stepwiseConfigurationInterface};
James Feist1fe08952019-05-07 09:17:16 -0700246
247 // this list only needs to be created once
248 if (!matches.empty())
249 {
250 return;
251 }
252
253 // we restart when the configuration changes or there are new sensors
254 for (const auto& interface : interfaces)
255 {
256 matches.emplace_back(
257 bus,
258 "type='signal',member='PropertiesChanged',arg0namespace='" +
259 interface + "'",
260 eventHandler, &timer);
261 }
262 matches.emplace_back(
263 bus,
264 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
265 "sensors/'",
266 eventHandler, &timer);
Jinliang Wangc2a311b2023-04-26 18:36:56 +0000267 matches.emplace_back(bus,
268 "type='signal',member='InterfacesRemoved',arg0path='/"
269 "xyz/openbmc_project/sensors/'",
270 eventHandler, &timer);
James Feist1fe08952019-05-07 09:17:16 -0700271}
272
Jason Ling6fc301f2020-07-23 12:39:57 -0700273/**
274 * retrieve an attribute from the pid configuration map
275 * @param[in] base - the PID configuration map, keys are the attributes and
276 * value is the variant associated with that attribute.
277 * @param attributeName - the name of the attribute
278 * @return a variant holding the value associated with a key
279 * @throw runtime_error : attributeName is not in base
280 */
281inline DbusVariantType getPIDAttribute(
282 const std::unordered_map<std::string, DbusVariantType>& base,
283 const std::string& attributeName)
284{
285 auto search = base.find(attributeName);
286 if (search == base.end())
287 {
288 throw std::runtime_error("missing attribute " + attributeName);
289 }
290 return search->second;
291}
292
Harvey Wu239aa7d2022-11-18 08:43:34 +0800293inline void getCycleTimeSetting(
294 const std::unordered_map<std::string, DbusVariantType>& zone,
295 const int zoneIndex, const std::string& attributeName, uint64_t& value)
296{
297 auto findAttributeName = zone.find(attributeName);
298 if (findAttributeName != zone.end())
299 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400300 double tmpAttributeValue =
301 std::visit(VariantToDoubleVisitor(), zone.at(attributeName));
Harvey Wu239aa7d2022-11-18 08:43:34 +0800302 if (tmpAttributeValue >= 1.0)
303 {
304 value = static_cast<uint64_t>(tmpAttributeValue);
305 }
306 else
307 {
308 std::cerr << "Zone " << zoneIndex << ": " << attributeName
309 << " is invalid. Use default " << value << " ms\n";
310 }
311 }
312 else
313 {
314 std::cerr << "Zone " << zoneIndex << ": " << attributeName
315 << " cannot find setting. Use default " << value << " ms\n";
316 }
317}
318
James Feist5ec20272019-07-10 11:59:57 -0700319void populatePidInfo(
Patrick Williamscd1e78a2025-04-07 17:21:05 -0400320 sdbusplus::bus_t& bus,
James Feist5ec20272019-07-10 11:59:57 -0700321 const std::unordered_map<std::string, DbusVariantType>& base,
Patrick Venture1df9e872020-10-08 15:35:01 -0700322 conf::ControllerInfo& info, const std::string* thresholdProperty,
Patrick Venture73823182020-10-08 15:12:51 -0700323 const std::map<std::string, conf::SensorConfig>& sensorConfig)
James Feist5ec20272019-07-10 11:59:57 -0700324{
Jason Ling6fc301f2020-07-23 12:39:57 -0700325 info.type = std::get<std::string>(getPIDAttribute(base, "Class"));
James Feist5ec20272019-07-10 11:59:57 -0700326 if (info.type == "fan")
327 {
328 info.setpoint = 0;
329 }
330 else
331 {
Jason Ling6fc301f2020-07-23 12:39:57 -0700332 info.setpoint = std::visit(VariantToDoubleVisitor(),
333 getPIDAttribute(base, "SetPoint"));
James Feist5ec20272019-07-10 11:59:57 -0700334 }
335
ykchiu9fe3a3c2023-05-11 13:43:54 +0800336 int failsafepercent = 0;
337 auto findFailSafe = base.find("FailSafePercent");
338 if (findFailSafe != base.end())
339 {
340 failsafepercent = std::visit(VariantToDoubleVisitor(),
341 getPIDAttribute(base, "FailSafePercent"));
342 }
343 info.failSafePercent = failsafepercent;
344
James Feist5ec20272019-07-10 11:59:57 -0700345 if (thresholdProperty != nullptr)
346 {
347 std::string interface;
348 if (*thresholdProperty == "WarningHigh" ||
349 *thresholdProperty == "WarningLow")
350 {
351 interface = thresholds::warningInterface;
352 }
353 else
354 {
355 interface = thresholds::criticalInterface;
356 }
Josh Lehan31058fd2023-01-13 11:06:16 -0800357
358 // Although this checks only the first vector element for the
359 // named threshold, it is OK, because the SetPointOffset parser
360 // splits up the input into individual vectors, each with only a
361 // single element, if it detects that SetPointOffset is in use.
362 const std::string& path =
363 sensorConfig.at(info.inputs.front().name).readPath;
James Feist5ec20272019-07-10 11:59:57 -0700364
Patrick Williamscd1e78a2025-04-07 17:21:05 -0400365 DbusHelper helper(bus);
Patrick Venture9b936922020-08-10 11:28:39 -0700366 std::string service = helper.getService(interface, path);
James Feist5ec20272019-07-10 11:59:57 -0700367 double reading = 0;
368 try
369 {
Patrick Venture9b936922020-08-10 11:28:39 -0700370 helper.getProperty(service, path, interface, *thresholdProperty,
371 reading);
James Feist5ec20272019-07-10 11:59:57 -0700372 }
Patrick Williamsb228bc32022-07-22 19:26:56 -0500373 catch (const sdbusplus::exception_t& ex)
James Feist5ec20272019-07-10 11:59:57 -0700374 {
375 // unsupported threshold, leaving reading at 0
376 }
377
378 info.setpoint += reading;
379 }
380
381 info.pidInfo.ts = 1.0; // currently unused
Jason Ling6fc301f2020-07-23 12:39:57 -0700382 info.pidInfo.proportionalCoeff = std::visit(
383 VariantToDoubleVisitor(), getPIDAttribute(base, "PCoefficient"));
384 info.pidInfo.integralCoeff = std::visit(
385 VariantToDoubleVisitor(), getPIDAttribute(base, "ICoefficient"));
Josh Lehanc612c052022-12-12 09:56:47 -0800386 // DCoefficient is below, it is optional, same reason as in buildjson.cpp
Jason Ling6fc301f2020-07-23 12:39:57 -0700387 info.pidInfo.feedFwdOffset = std::visit(
388 VariantToDoubleVisitor(), getPIDAttribute(base, "FFOffCoefficient"));
389 info.pidInfo.feedFwdGain = std::visit(
390 VariantToDoubleVisitor(), getPIDAttribute(base, "FFGainCoefficient"));
391 info.pidInfo.integralLimit.max = std::visit(
392 VariantToDoubleVisitor(), getPIDAttribute(base, "ILimitMax"));
393 info.pidInfo.integralLimit.min = std::visit(
394 VariantToDoubleVisitor(), getPIDAttribute(base, "ILimitMin"));
395 info.pidInfo.outLim.max = std::visit(VariantToDoubleVisitor(),
396 getPIDAttribute(base, "OutLimitMax"));
397 info.pidInfo.outLim.min = std::visit(VariantToDoubleVisitor(),
398 getPIDAttribute(base, "OutLimitMin"));
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400399 info.pidInfo.slewNeg =
400 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewNeg"));
401 info.pidInfo.slewPos =
402 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewPos"));
Josh Lehanc612c052022-12-12 09:56:47 -0800403
Delphine CC Chiu97889632023-11-06 11:32:46 +0800404 bool checkHysterWithSetpt = false;
James Feist5ec20272019-07-10 11:59:57 -0700405 double negativeHysteresis = 0;
406 double positiveHysteresis = 0;
Josh Lehanc612c052022-12-12 09:56:47 -0800407 double derivativeCoeff = 0;
James Feist5ec20272019-07-10 11:59:57 -0700408
Delphine CC Chiu5d897e22024-06-04 13:33:22 +0800409 auto findCheckHysterFlag = base.find("CheckHysteresisWithSetpoint");
James Feist5ec20272019-07-10 11:59:57 -0700410 auto findNeg = base.find("NegativeHysteresis");
411 auto findPos = base.find("PositiveHysteresis");
Josh Lehanc612c052022-12-12 09:56:47 -0800412 auto findDerivative = base.find("DCoefficient");
James Feist5ec20272019-07-10 11:59:57 -0700413
Delphine CC Chiu97889632023-11-06 11:32:46 +0800414 if (findCheckHysterFlag != base.end())
415 {
416 checkHysterWithSetpt = std::get<bool>(findCheckHysterFlag->second);
417 }
James Feist5ec20272019-07-10 11:59:57 -0700418 if (findNeg != base.end())
419 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400420 negativeHysteresis =
421 std::visit(VariantToDoubleVisitor(), findNeg->second);
James Feist5ec20272019-07-10 11:59:57 -0700422 }
James Feist5ec20272019-07-10 11:59:57 -0700423 if (findPos != base.end())
424 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400425 positiveHysteresis =
426 std::visit(VariantToDoubleVisitor(), findPos->second);
James Feist5ec20272019-07-10 11:59:57 -0700427 }
Josh Lehanc612c052022-12-12 09:56:47 -0800428 if (findDerivative != base.end())
429 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400430 derivativeCoeff =
431 std::visit(VariantToDoubleVisitor(), findDerivative->second);
Josh Lehanc612c052022-12-12 09:56:47 -0800432 }
433
Delphine CC Chiu97889632023-11-06 11:32:46 +0800434 info.pidInfo.checkHysterWithSetpt = checkHysterWithSetpt;
James Feist5ec20272019-07-10 11:59:57 -0700435 info.pidInfo.negativeHysteresis = negativeHysteresis;
436 info.pidInfo.positiveHysteresis = positiveHysteresis;
Josh Lehanc612c052022-12-12 09:56:47 -0800437 info.pidInfo.derivativeCoeff = derivativeCoeff;
James Feist5ec20272019-07-10 11:59:57 -0700438}
439
Patrick Williamsb228bc32022-07-22 19:26:56 -0500440bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
Patrick Venture73823182020-10-08 15:12:51 -0700441 std::map<std::string, conf::SensorConfig>& sensorConfig,
442 std::map<int64_t, conf::PIDConf>& zoneConfig,
443 std::map<int64_t, conf::ZoneConfig>& zoneDetailsConfig)
James Feist1fe08952019-05-07 09:17:16 -0700444{
James Feist1fe08952019-05-07 09:17:16 -0700445 sensorConfig.clear();
446 zoneConfig.clear();
447 zoneDetailsConfig.clear();
448
449 createMatches(bus, timer);
450
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400451 auto mapper =
452 bus.new_method_call("xyz.openbmc_project.ObjectMapper",
453 "/xyz/openbmc_project/object_mapper",
454 "xyz.openbmc_project.ObjectMapper", "GetSubTree");
455 mapper.append(
456 "/", 0,
457 std::array<const char*, 6>{
458 objectManagerInterface, pidConfigurationInterface,
459 pidZoneConfigurationInterface, stepwiseConfigurationInterface,
460 sensorInterface, defaultPwmInterface});
James Feist7136a5a2018-07-19 09:52:05 -0700461 std::unordered_map<
462 std::string, std::unordered_map<std::string, std::vector<std::string>>>
463 respData;
James Feist22c257a2018-08-31 14:07:12 -0700464 try
465 {
466 auto resp = bus.call(mapper);
James Feist22c257a2018-08-31 14:07:12 -0700467 resp.read(respData);
468 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500469 catch (const sdbusplus::exception_t&)
James Feist22c257a2018-08-31 14:07:12 -0700470 {
471 // can't do anything without mapper call data
472 throw std::runtime_error("ObjectMapper Call Failure");
473 }
James Feist7136a5a2018-07-19 09:52:05 -0700474
James Feist7136a5a2018-07-19 09:52:05 -0700475 if (respData.empty())
476 {
James Feist22c257a2018-08-31 14:07:12 -0700477 // can't do anything without mapper call data
James Feist7136a5a2018-07-19 09:52:05 -0700478 throw std::runtime_error("No configuration data available from Mapper");
479 }
480 // create a map of pair of <has pid configuration, ObjectManager path>
481 std::unordered_map<std::string, std::pair<bool, std::string>> owners;
482 // and a map of <path, interface> for sensors
483 std::unordered_map<std::string, std::string> sensors;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700484 for (const auto& objectPair : respData)
James Feist7136a5a2018-07-19 09:52:05 -0700485 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700486 for (const auto& ownerPair : objectPair.second)
James Feist7136a5a2018-07-19 09:52:05 -0700487 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700488 auto& owner = owners[ownerPair.first];
489 for (const std::string& interface : ownerPair.second)
James Feist7136a5a2018-07-19 09:52:05 -0700490 {
James Feist7136a5a2018-07-19 09:52:05 -0700491 if (interface == objectManagerInterface)
492 {
493 owner.second = objectPair.first;
494 }
495 if (interface == pidConfigurationInterface ||
James Feist22c257a2018-08-31 14:07:12 -0700496 interface == pidZoneConfigurationInterface ||
497 interface == stepwiseConfigurationInterface)
James Feist7136a5a2018-07-19 09:52:05 -0700498 {
499 owner.first = true;
500 }
Patrick Venture0911bfe2020-08-10 12:51:40 -0700501 if (interface == sensorInterface ||
502 interface == defaultPwmInterface)
James Feist7136a5a2018-07-19 09:52:05 -0700503 {
504 // we're not interested in pwm sensors, just pwm control
505 if (interface == sensorInterface &&
506 objectPair.first.find("pwm") != std::string::npos)
507 {
508 continue;
509 }
510 sensors[objectPair.first] = interface;
511 }
512 }
513 }
514 }
515 ManagedObjectType configurations;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700516 for (const auto& owner : owners)
James Feist7136a5a2018-07-19 09:52:05 -0700517 {
518 // skip if no pid configuration (means probably a sensor)
519 if (!owner.second.first)
520 {
521 continue;
522 }
523 auto endpoint = bus.new_method_call(
524 owner.first.c_str(), owner.second.second.c_str(),
525 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
James Feist22c257a2018-08-31 14:07:12 -0700526 ManagedObjectType configuration;
527 try
James Feist7136a5a2018-07-19 09:52:05 -0700528 {
Manojkiran Eda7ca88872024-06-17 11:55:48 +0530529 auto response = bus.call(endpoint);
530 response.read(configuration);
James Feist22c257a2018-08-31 14:07:12 -0700531 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500532 catch (const sdbusplus::exception_t&)
James Feist22c257a2018-08-31 14:07:12 -0700533 {
534 // this shouldn't happen, probably means daemon crashed
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400535 throw std::runtime_error(
536 "Error getting managed objects from " + owner.first);
James Feist7136a5a2018-07-19 09:52:05 -0700537 }
James Feist22c257a2018-08-31 14:07:12 -0700538
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700539 for (auto& pathPair : configuration)
James Feist7136a5a2018-07-19 09:52:05 -0700540 {
541 if (pathPair.second.find(pidConfigurationInterface) !=
542 pathPair.second.end() ||
543 pathPair.second.find(pidZoneConfigurationInterface) !=
James Feist22c257a2018-08-31 14:07:12 -0700544 pathPair.second.end() ||
545 pathPair.second.find(stepwiseConfigurationInterface) !=
James Feist7136a5a2018-07-19 09:52:05 -0700546 pathPair.second.end())
547 {
548 configurations.emplace(pathPair);
549 }
James Feistf0096a02019-02-21 11:25:22 -0800550 }
551 }
552
553 // remove controllers from config that aren't in the current profile(s)
James Feist3987c8b2019-05-13 10:43:17 -0700554 std::vector<std::string> selectedProfiles = getSelectedProfiles(bus);
555 if (selectedProfiles.size())
James Feistf0096a02019-02-21 11:25:22 -0800556 {
James Feist3987c8b2019-05-13 10:43:17 -0700557 for (auto pathIt = configurations.begin();
558 pathIt != configurations.end();)
James Feistf0096a02019-02-21 11:25:22 -0800559 {
James Feist3987c8b2019-05-13 10:43:17 -0700560 for (auto confIt = pathIt->second.begin();
561 confIt != pathIt->second.end();)
James Feistf0096a02019-02-21 11:25:22 -0800562 {
James Feist3987c8b2019-05-13 10:43:17 -0700563 auto profilesFind = confIt->second.find("Profiles");
564 if (profilesFind == confIt->second.end())
James Feistf0096a02019-02-21 11:25:22 -0800565 {
James Feist3987c8b2019-05-13 10:43:17 -0700566 confIt++;
567 continue; // if no profiles selected, apply always
568 }
569 auto profiles =
570 std::get<std::vector<std::string>>(profilesFind->second);
571 if (profiles.empty())
572 {
573 confIt++;
574 continue;
575 }
576
577 bool found = false;
578 for (const std::string& profile : profiles)
579 {
580 if (std::find(selectedProfiles.begin(),
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400581 selectedProfiles.end(), profile) !=
582 selectedProfiles.end())
James Feist3987c8b2019-05-13 10:43:17 -0700583 {
584 found = true;
585 break;
586 }
587 }
588 if (found)
589 {
590 confIt++;
James Feistf0096a02019-02-21 11:25:22 -0800591 }
592 else
593 {
James Feist3987c8b2019-05-13 10:43:17 -0700594 confIt = pathIt->second.erase(confIt);
James Feistf0096a02019-02-21 11:25:22 -0800595 }
596 }
James Feist3987c8b2019-05-13 10:43:17 -0700597 if (pathIt->second.empty())
James Feistf0096a02019-02-21 11:25:22 -0800598 {
James Feist3987c8b2019-05-13 10:43:17 -0700599 pathIt = configurations.erase(pathIt);
James Feistf0096a02019-02-21 11:25:22 -0800600 }
James Feist3987c8b2019-05-13 10:43:17 -0700601 else
James Feistf0096a02019-02-21 11:25:22 -0800602 {
James Feist3987c8b2019-05-13 10:43:17 -0700603 pathIt++;
James Feistf0096a02019-02-21 11:25:22 -0800604 }
James Feist7136a5a2018-07-19 09:52:05 -0700605 }
606 }
James Feist8c3c51e2018-08-08 16:31:43 -0700607
Josh Lehan998fbe62020-09-20 21:21:05 -0700608 // On D-Bus, although not necessary,
609 // having the "zoneID" field can still be useful,
610 // as it is used for diagnostic messages,
611 // logging file names, and so on.
612 // Accept optional "ZoneIndex" parameter to explicitly specify.
613 // If not present, or not unique, auto-assign index,
614 // using 0-based numbering, ensuring uniqueness.
615 std::map<std::string, int64_t> foundZones;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700616 for (const auto& configuration : configurations)
James Feist7136a5a2018-07-19 09:52:05 -0700617 {
618 auto findZone =
619 configuration.second.find(pidZoneConfigurationInterface);
620 if (findZone != configuration.second.end())
621 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700622 const auto& zone = findZone->second;
James Feistffd418b2018-11-15 14:46:36 -0800623
James Feist1f802f52019-02-08 13:51:43 -0800624 const std::string& name = std::get<std::string>(zone.at("Name"));
Josh Lehan998fbe62020-09-20 21:21:05 -0700625
626 auto findZoneIndex = zone.find("ZoneIndex");
627 if (findZoneIndex == zone.end())
628 {
629 continue;
630 }
631
632 auto ptrZoneIndex = std::get_if<double>(&(findZoneIndex->second));
633 if (!ptrZoneIndex)
634 {
635 continue;
636 }
637
638 auto desiredIndex = static_cast<int64_t>(*ptrZoneIndex);
639 auto grantedIndex = setZoneIndex(name, foundZones, desiredIndex);
640 std::cout << "Zone " << name << " is at ZoneIndex " << grantedIndex
641 << "\n";
642 }
643 }
644
645 for (const auto& configuration : configurations)
646 {
647 auto findZone =
648 configuration.second.find(pidZoneConfigurationInterface);
649 if (findZone != configuration.second.end())
650 {
651 const auto& zone = findZone->second;
652
653 const std::string& name = std::get<std::string>(zone.at("Name"));
654
655 auto index = getZoneIndex(name, foundZones);
James Feist8c3c51e2018-08-08 16:31:43 -0700656
Patrick Venturec54fbd82018-10-30 19:40:05 -0700657 auto& details = zoneDetailsConfig[index];
Josh Lehan998fbe62020-09-20 21:21:05 -0700658
James Feist3484bed2019-02-25 13:28:18 -0800659 details.minThermalOutput = std::visit(VariantToDoubleVisitor(),
660 zone.at("MinThermalOutput"));
ykchiu9fe3a3c2023-05-11 13:43:54 +0800661
662 int failsafepercent = 0;
663 auto findFailSafe = zone.find("FailSafePercent");
664 if (findFailSafe != zone.end())
665 {
666 failsafepercent = std::visit(VariantToDoubleVisitor(),
667 zone.at("FailSafePercent"));
668 }
669 details.failsafePercent = failsafepercent;
Josh Lehan9f9a06a2022-12-14 10:39:45 -0800670
Harvey Wu239aa7d2022-11-18 08:43:34 +0800671 getCycleTimeSetting(zone, index, "CycleIntervalTimeMS",
672 details.cycleTime.cycleIntervalTimeMS);
673 getCycleTimeSetting(zone, index, "UpdateThermalsTimeMS",
674 details.cycleTime.updateThermalsTimeMS);
Delphine CC Chiu97889632023-11-06 11:32:46 +0800675
676 bool accumulateSetPoint = false;
677 auto findAccSetPoint = zone.find("AccumulateSetPoint");
678 if (findAccSetPoint != zone.end())
679 {
680 accumulateSetPoint = std::get<bool>(findAccSetPoint->second);
681 }
682 details.accumulateSetPoint = accumulateSetPoint;
James Feist7136a5a2018-07-19 09:52:05 -0700683 }
684 auto findBase = configuration.second.find(pidConfigurationInterface);
Jason Lingf3b04fd2020-07-24 09:33:04 -0700685 // loop through all the PID configurations and fill out a sensor config
James Feist22c257a2018-08-31 14:07:12 -0700686 if (findBase != configuration.second.end())
James Feist7136a5a2018-07-19 09:52:05 -0700687 {
James Feist22c257a2018-08-31 14:07:12 -0700688 const auto& base =
689 configuration.second.at(pidConfigurationInterface);
ykchiu7c6d35d2023-05-10 17:01:46 +0800690 const std::string pidName =
691 sensorNameToDbusName(std::get<std::string>(base.at("Name")));
Jason Lingf3b04fd2020-07-24 09:33:04 -0700692 const std::string pidClass =
693 std::get<std::string>(base.at("Class"));
James Feist22c257a2018-08-31 14:07:12 -0700694 const std::vector<std::string>& zones =
James Feist1f802f52019-02-08 13:51:43 -0800695 std::get<std::vector<std::string>>(base.at("Zones"));
James Feist22c257a2018-08-31 14:07:12 -0700696 for (const std::string& zone : zones)
James Feist7136a5a2018-07-19 09:52:05 -0700697 {
Josh Lehan998fbe62020-09-20 21:21:05 -0700698 auto index = getZoneIndex(zone, foundZones);
699
James Feistf81f2882019-02-26 11:26:36 -0800700 conf::PIDConf& conf = zoneConfig[index];
Jason Lingf3b04fd2020-07-24 09:33:04 -0700701 std::vector<std::string> inputSensorNames(
702 std::get<std::vector<std::string>>(base.at("Inputs")));
703 std::vector<std::string> outputSensorNames;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800704 std::vector<std::string> missingAcceptableSensorNames;
Chaul Lya552fe22024-11-15 10:20:28 +0000705 std::vector<std::string> archivedInputSensorNames;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800706
707 auto findMissingAcceptable = base.find("MissingIsAcceptable");
708 if (findMissingAcceptable != base.end())
709 {
710 missingAcceptableSensorNames =
711 std::get<std::vector<std::string>>(
712 findMissingAcceptable->second);
713 }
James Feist50fdfe32018-09-24 15:51:09 -0700714
Jason Lingf3b04fd2020-07-24 09:33:04 -0700715 // assumption: all fan pids must have at least one output
716 if (pidClass == "fan")
James Feist50fdfe32018-09-24 15:51:09 -0700717 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700718 outputSensorNames = std::get<std::vector<std::string>>(
719 getPIDAttribute(base, "Outputs"));
James Feist50fdfe32018-09-24 15:51:09 -0700720 }
James Feist1738e2a2019-02-04 15:57:03 -0800721
Alex.Song8f73ad72021-10-07 00:18:27 +0800722 bool unavailableAsFailed = true;
723 auto findUnavailableAsFailed =
724 base.find("InputUnavailableAsFailed");
725 if (findUnavailableAsFailed != base.end())
726 {
727 unavailableAsFailed =
728 std::get<bool>(findUnavailableAsFailed->second);
729 }
730
Jason Lingf3b04fd2020-07-24 09:33:04 -0700731 std::vector<SensorInterfaceType> inputSensorInterfaces;
732 std::vector<SensorInterfaceType> outputSensorInterfaces;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800733 std::vector<SensorInterfaceType>
734 missingAcceptableSensorInterfaces;
735
Jason Lingf3b04fd2020-07-24 09:33:04 -0700736 /* populate an interface list for different sensor direction
737 * types (input,output)
738 */
739 /* take the Inputs from the configuration and generate
740 * a list of dbus descriptors (path, interface).
741 * Mapping can be many-to-one since an element of Inputs can be
742 * a regex
743 */
744 for (const std::string& sensorName : inputSensorNames)
James Feist50fdfe32018-09-24 15:51:09 -0700745 {
Chaul Lya552fe22024-11-15 10:20:28 +0000746#ifndef HANDLE_MISSING_OBJECT_PATHS
Jason Lingf3b04fd2020-07-24 09:33:04 -0700747 findSensors(sensors, sensorNameToDbusName(sensorName),
748 inputSensorInterfaces);
Chaul Lya552fe22024-11-15 10:20:28 +0000749#else
750 std::vector<std::pair<std::string, std::string>>
751 sensorPathIfacePairs;
752 auto found =
753 findSensors(sensors, sensorNameToDbusName(sensorName),
754 sensorPathIfacePairs);
755 if (found)
756 {
757 inputSensorInterfaces.insert(
758 inputSensorInterfaces.end(),
759 sensorPathIfacePairs.begin(),
760 sensorPathIfacePairs.end());
761 }
762 else if (pidClass != "fan")
763 {
764 if (std::find(missingAcceptableSensorNames.begin(),
765 missingAcceptableSensorNames.end(),
766 sensorName) ==
767 missingAcceptableSensorNames.end())
768 {
769 std::cerr
770 << "Pid controller: Missing a missing-unacceptable sensor from D-Bus "
771 << sensorName << "\n";
772 std::string inputSensorName =
773 sensorNameToDbusName(sensorName);
774 auto& config = sensorConfig[inputSensorName];
775 archivedInputSensorNames.push_back(inputSensorName);
776 config.type = pidClass;
777 config.readPath =
778 getSensorPath(config.type, inputSensorName);
779 config.timeout = 0;
780 config.ignoreDbusMinMax = true;
781 config.unavailableAsFailed = unavailableAsFailed;
782 }
783 else
784 {
785 // When an input sensor is NOT on DBus, and it's in
786 // the MissingIsAcceptable list. Ignore it and
787 // continue with the next input sensor.
788 std::cout
789 << "Pid controller: Missing a missing-acceptable sensor from D-Bus "
790 << sensorName << "\n";
791 continue;
792 }
793 }
794#endif
Jason Lingf3b04fd2020-07-24 09:33:04 -0700795 }
796 for (const std::string& sensorName : outputSensorNames)
797 {
798 findSensors(sensors, sensorNameToDbusName(sensorName),
799 outputSensorInterfaces);
James Feist1738e2a2019-02-04 15:57:03 -0800800 }
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800801 for (const std::string& sensorName :
802 missingAcceptableSensorNames)
803 {
804 findSensors(sensors, sensorNameToDbusName(sensorName),
805 missingAcceptableSensorInterfaces);
806 }
James Feist50fdfe32018-09-24 15:51:09 -0700807
Jason Lingf3b04fd2020-07-24 09:33:04 -0700808 for (const SensorInterfaceType& inputSensorInterface :
809 inputSensorInterfaces)
James Feist1738e2a2019-02-04 15:57:03 -0800810 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700811 const std::string& dbusInterface =
812 inputSensorInterface.second;
813 const std::string& inputSensorPath =
814 inputSensorInterface.first;
Josh Lehanfb82a872020-09-20 21:48:22 -0700815
816 // Setting timeout to 0 is intentional, as D-Bus passive
817 // sensor updates are pushed in, not pulled by timer poll.
818 // Setting ignoreDbusMinMax is intentional, as this
819 // prevents normalization of values to [0.0, 1.0] range,
820 // which would mess up the PID loop math.
821 // All non-fan PID classes should be initialized this way.
822 // As for why a fan should not use this code path, see
823 // the ed1dafdf168def37c65bfb7a5efd18d9dbe04727 commit.
Josh Lehan23e22b92022-11-12 22:37:58 -0800824 if ((pidClass == "temp") || (pidClass == "margin") ||
825 (pidClass == "power") || (pidClass == "powersum"))
James Feist50fdfe32018-09-24 15:51:09 -0700826 {
Harvey.Wued1dafd2022-02-09 13:53:20 +0800827 std::string inputSensorName =
828 getSensorNameFromPath(inputSensorPath);
829 auto& config = sensorConfig[inputSensorName];
Chaul Lya552fe22024-11-15 10:20:28 +0000830 archivedInputSensorNames.push_back(inputSensorName);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800831 config.type = pidClass;
832 config.readPath = inputSensorInterface.first;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700833 config.timeout = 0;
834 config.ignoreDbusMinMax = true;
Alex.Song8f73ad72021-10-07 00:18:27 +0800835 config.unavailableAsFailed = unavailableAsFailed;
James Feist50fdfe32018-09-24 15:51:09 -0700836 }
Josh Lehanfb82a872020-09-20 21:48:22 -0700837
Jason Lingf3b04fd2020-07-24 09:33:04 -0700838 if (dbusInterface != sensorInterface)
James Feist50fdfe32018-09-24 15:51:09 -0700839 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700840 /* all expected inputs in the configuration are expected
841 * to be sensor interfaces
842 */
Ed Tanous7d6e2252025-06-27 10:45:25 -0700843 throw std::runtime_error(std::format(
844 "sensor at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
845 inputSensorPath, dbusInterface, sensorInterface));
James Feist50fdfe32018-09-24 15:51:09 -0700846 }
847 }
James Feist1738e2a2019-02-04 15:57:03 -0800848
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800849 // MissingIsAcceptable same postprocessing as Inputs
850 missingAcceptableSensorNames.clear();
851 for (const SensorInterfaceType&
852 missingAcceptableSensorInterface :
853 missingAcceptableSensorInterfaces)
854 {
855 const std::string& dbusInterface =
856 missingAcceptableSensorInterface.second;
857 const std::string& missingAcceptableSensorPath =
858 missingAcceptableSensorInterface.first;
859
860 std::string missingAcceptableSensorName =
861 getSensorNameFromPath(missingAcceptableSensorPath);
862 missingAcceptableSensorNames.push_back(
863 missingAcceptableSensorName);
864
865 if (dbusInterface != sensorInterface)
866 {
867 /* MissingIsAcceptable same error checking as Inputs
868 */
Ed Tanous7d6e2252025-06-27 10:45:25 -0700869 throw std::runtime_error(std::format(
870 "sensor at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
871 missingAcceptableSensorPath, dbusInterface,
872 sensorInterface));
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800873 }
874 }
875
Jason Lingf3b04fd2020-07-24 09:33:04 -0700876 /* fan pids need to pair up tach sensors with their pwm
877 * counterparts
878 */
879 if (pidClass == "fan")
880 {
881 /* If a PID is a fan there should be either
882 * (1) one output(pwm) per input(tach)
883 * OR
884 * (2) one putput(pwm) for all inputs(tach)
885 * everything else indicates a bad configuration.
886 */
887 bool singlePwm = false;
888 if (outputSensorInterfaces.size() == 1)
889 {
890 /* one pwm, set write paths for all fan sensors to it */
891 singlePwm = true;
892 }
893 else if (inputSensorInterfaces.size() ==
894 outputSensorInterfaces.size())
895 {
896 /* one to one mapping, each fan sensor gets its own pwm
897 * control */
898 singlePwm = false;
899 }
900 else
901 {
902 throw std::runtime_error(
903 "fan PID has invalid number of Outputs");
904 }
905 std::string fanSensorName;
906 std::string pwmPath;
907 std::string pwmInterface;
Harvey.Wued1dafd2022-02-09 13:53:20 +0800908 std::string pwmSensorName;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700909 if (singlePwm)
910 {
911 /* if just a single output(pwm) is provided then use
912 * that pwm control path for all the fan sensor write
913 * path configs
914 */
915 pwmPath = outputSensorInterfaces.at(0).first;
916 pwmInterface = outputSensorInterfaces.at(0).second;
917 }
918 for (uint32_t idx = 0; idx < inputSensorInterfaces.size();
919 idx++)
920 {
921 if (!singlePwm)
922 {
923 pwmPath = outputSensorInterfaces.at(idx).first;
924 pwmInterface =
925 outputSensorInterfaces.at(idx).second;
926 }
Patrick Venture0911bfe2020-08-10 12:51:40 -0700927 if (defaultPwmInterface != pwmInterface)
Jason Lingf3b04fd2020-07-24 09:33:04 -0700928 {
Ed Tanous7d6e2252025-06-27 10:45:25 -0700929 throw std::runtime_error(std::format(
930 "fan pwm control at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
931 pwmPath, pwmInterface, defaultPwmInterface));
Jason Lingf3b04fd2020-07-24 09:33:04 -0700932 }
933 const std::string& fanPath =
934 inputSensorInterfaces.at(idx).first;
935 fanSensorName = getSensorNameFromPath(fanPath);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800936 pwmSensorName = getSensorNameFromPath(pwmPath);
937 std::string fanPwmIndex = fanSensorName + pwmSensorName;
Chaul Lya552fe22024-11-15 10:20:28 +0000938 archivedInputSensorNames.push_back(fanPwmIndex);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800939 auto& fanConfig = sensorConfig[fanPwmIndex];
940 fanConfig.type = pidClass;
941 fanConfig.readPath = fanPath;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700942 fanConfig.writePath = pwmPath;
943 // todo: un-hardcode this if there are fans with
944 // different ranges
945 fanConfig.max = 255;
946 fanConfig.min = 0;
947 }
948 }
James Feist11d243d2019-06-24 16:18:40 -0700949 // if the sensors aren't available in the current state, don't
950 // add them to the configuration.
Chaul Lya552fe22024-11-15 10:20:28 +0000951 if (archivedInputSensorNames.empty())
James Feist11d243d2019-06-24 16:18:40 -0700952 {
953 continue;
954 }
955
James Feist5ec20272019-07-10 11:59:57 -0700956 std::string offsetType;
James Feist50fdfe32018-09-24 15:51:09 -0700957
James Feist5ec20272019-07-10 11:59:57 -0700958 // SetPointOffset is a threshold value to pull from the sensor
959 // to apply an offset. For upper thresholds this means the
960 // setpoint is usually negative.
961 auto findSetpointOffset = base.find("SetPointOffset");
962 if (findSetpointOffset != base.end())
James Feist22c257a2018-08-31 14:07:12 -0700963 {
James Feist5ec20272019-07-10 11:59:57 -0700964 offsetType =
965 std::get<std::string>(findSetpointOffset->second);
966 if (std::find(thresholds::types.begin(),
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400967 thresholds::types.end(), offsetType) ==
968 thresholds::types.end())
James Feist5ec20272019-07-10 11:59:57 -0700969 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400970 throw std::runtime_error(
971 "Unsupported type: " + offsetType);
James Feist5ec20272019-07-10 11:59:57 -0700972 }
973 }
974
Josh Lehan31058fd2023-01-13 11:06:16 -0800975 std::vector<double> inputTempToMargin;
976
977 auto findTempToMargin = base.find("TempToMargin");
978 if (findTempToMargin != base.end())
979 {
980 inputTempToMargin =
981 std::get<std::vector<double>>(findTempToMargin->second);
982 }
983
984 std::vector<pid_control::conf::SensorInput> sensorInputs =
Chaul Lya552fe22024-11-15 10:20:28 +0000985 spliceInputs(archivedInputSensorNames, inputTempToMargin,
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800986 missingAcceptableSensorNames);
Josh Lehan31058fd2023-01-13 11:06:16 -0800987
James Feist5ec20272019-07-10 11:59:57 -0700988 if (offsetType.empty())
989 {
ykchiu7c6d35d2023-05-10 17:01:46 +0800990 conf::ControllerInfo& info = conf[pidName];
Josh Lehan31058fd2023-01-13 11:06:16 -0800991 info.inputs = std::move(sensorInputs);
Patrick Venture73823182020-10-08 15:12:51 -0700992 populatePidInfo(bus, base, info, nullptr, sensorConfig);
James Feist22c257a2018-08-31 14:07:12 -0700993 }
994 else
995 {
James Feist5ec20272019-07-10 11:59:57 -0700996 // we have to split up the inputs, as in practice t-control
997 // values will differ, making setpoints differ
Josh Lehan31058fd2023-01-13 11:06:16 -0800998 for (const pid_control::conf::SensorInput& input :
999 sensorInputs)
James Feist5ec20272019-07-10 11:59:57 -07001000 {
Josh Lehan31058fd2023-01-13 11:06:16 -08001001 conf::ControllerInfo& info = conf[input.name];
James Feist5ec20272019-07-10 11:59:57 -07001002 info.inputs.emplace_back(input);
Patrick Venture73823182020-10-08 15:12:51 -07001003 populatePidInfo(bus, base, info, &offsetType,
1004 sensorConfig);
James Feist5ec20272019-07-10 11:59:57 -07001005 }
James Feist22c257a2018-08-31 14:07:12 -07001006 }
James Feist22c257a2018-08-31 14:07:12 -07001007 }
1008 }
1009 auto findStepwise =
1010 configuration.second.find(stepwiseConfigurationInterface);
1011 if (findStepwise != configuration.second.end())
1012 {
1013 const auto& base = findStepwise->second;
ykchiu7c6d35d2023-05-10 17:01:46 +08001014 const std::string pidName =
1015 sensorNameToDbusName(std::get<std::string>(base.at("Name")));
James Feist22c257a2018-08-31 14:07:12 -07001016 const std::vector<std::string>& zones =
James Feist1f802f52019-02-08 13:51:43 -08001017 std::get<std::vector<std::string>>(base.at("Zones"));
James Feist22c257a2018-08-31 14:07:12 -07001018 for (const std::string& zone : zones)
1019 {
Josh Lehan998fbe62020-09-20 21:21:05 -07001020 auto index = getZoneIndex(zone, foundZones);
1021
James Feistf81f2882019-02-26 11:26:36 -08001022 conf::PIDConf& conf = zoneConfig[index];
James Feist50fdfe32018-09-24 15:51:09 -07001023
1024 std::vector<std::string> inputs;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001025 std::vector<std::string> missingAcceptableSensors;
1026 std::vector<std::string> missingAcceptableSensorNames;
James Feist50fdfe32018-09-24 15:51:09 -07001027 std::vector<std::string> sensorNames =
James Feist1f802f52019-02-08 13:51:43 -08001028 std::get<std::vector<std::string>>(base.at("Inputs"));
James Feist50fdfe32018-09-24 15:51:09 -07001029
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001030 auto findMissingAcceptable = base.find("MissingIsAcceptable");
1031 if (findMissingAcceptable != base.end())
1032 {
1033 missingAcceptableSensorNames =
1034 std::get<std::vector<std::string>>(
1035 findMissingAcceptable->second);
1036 }
1037
Alex.Song8f73ad72021-10-07 00:18:27 +08001038 bool unavailableAsFailed = true;
1039 auto findUnavailableAsFailed =
1040 base.find("InputUnavailableAsFailed");
1041 if (findUnavailableAsFailed != base.end())
1042 {
1043 unavailableAsFailed =
1044 std::get<bool>(findUnavailableAsFailed->second);
1045 }
1046
James Feist1738e2a2019-02-04 15:57:03 -08001047 bool sensorFound = false;
James Feist50fdfe32018-09-24 15:51:09 -07001048 for (const std::string& sensorName : sensorNames)
1049 {
James Feist1738e2a2019-02-04 15:57:03 -08001050 std::vector<std::pair<std::string, std::string>>
1051 sensorPathIfacePairs;
Jason Lingf3b04fd2020-07-24 09:33:04 -07001052 if (!findSensors(sensors, sensorNameToDbusName(sensorName),
1053 sensorPathIfacePairs))
James Feist50fdfe32018-09-24 15:51:09 -07001054 {
Chaul Lya552fe22024-11-15 10:20:28 +00001055#ifndef HANDLE_MISSING_OBJECT_PATHS
James Feist50fdfe32018-09-24 15:51:09 -07001056 break;
Chaul Lya552fe22024-11-15 10:20:28 +00001057#else
1058 if (std::find(missingAcceptableSensorNames.begin(),
1059 missingAcceptableSensorNames.end(),
1060 sensorName) ==
1061 missingAcceptableSensorNames.end())
1062 {
1063 // When an input sensor is NOT on DBus, and it's NOT
1064 // in the MissingIsAcceptable list. Build it as a
1065 // failed sensor with default information (temp
1066 // sensor path, temp type, ...)
1067 std::cerr
1068 << "Stepwise controller: Missing a missing-unacceptable sensor from D-Bus "
1069 << sensorName << "\n";
1070 std::string shortName =
1071 sensorNameToDbusName(sensorName);
1072
1073 inputs.push_back(shortName);
1074 auto& config = sensorConfig[shortName];
1075 config.type = "temp";
1076 config.readPath =
1077 getSensorPath(config.type, shortName);
1078 config.ignoreDbusMinMax = true;
1079 config.unavailableAsFailed = unavailableAsFailed;
1080 // todo: maybe un-hardcode this if we run into
1081 // slower timeouts with sensors
1082
1083 config.timeout = 0;
1084 sensorFound = true;
1085 }
1086 else
1087 {
1088 // When an input sensor is NOT on DBus, and it's in
1089 // the MissingIsAcceptable list. Ignore it and
1090 // continue with the next input sensor.
1091 std::cout
1092 << "Stepwise controller: Missing a missing-acceptable sensor from D-Bus "
1093 << sensorName << "\n";
1094 continue;
1095 }
1096#endif
James Feist50fdfe32018-09-24 15:51:09 -07001097 }
Chaul Lya552fe22024-11-15 10:20:28 +00001098 else
James Feist1738e2a2019-02-04 15:57:03 -08001099 {
Chaul Lya552fe22024-11-15 10:20:28 +00001100 for (const auto& sensorPathIfacePair :
1101 sensorPathIfacePairs)
1102 {
1103 std::string shortName = getSensorNameFromPath(
1104 sensorPathIfacePair.first);
James Feist50fdfe32018-09-24 15:51:09 -07001105
Chaul Lya552fe22024-11-15 10:20:28 +00001106 inputs.push_back(shortName);
1107 auto& config = sensorConfig[shortName];
1108 config.readPath = sensorPathIfacePair.first;
1109 config.type = "temp";
1110 config.ignoreDbusMinMax = true;
1111 config.unavailableAsFailed = unavailableAsFailed;
1112 // todo: maybe un-hardcode this if we run into
1113 // slower timeouts with sensors
James Feist1738e2a2019-02-04 15:57:03 -08001114
Chaul Lya552fe22024-11-15 10:20:28 +00001115 config.timeout = 0;
1116 sensorFound = true;
1117 }
James Feist1738e2a2019-02-04 15:57:03 -08001118 }
James Feist50fdfe32018-09-24 15:51:09 -07001119 }
1120 if (!sensorFound)
1121 {
1122 continue;
1123 }
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001124
1125 // MissingIsAcceptable same postprocessing as Inputs
1126 for (const std::string& missingAcceptableSensorName :
1127 missingAcceptableSensorNames)
1128 {
1129 std::vector<std::pair<std::string, std::string>>
1130 sensorPathIfacePairs;
1131 if (!findSensors(
1132 sensors,
1133 sensorNameToDbusName(missingAcceptableSensorName),
1134 sensorPathIfacePairs))
1135 {
Chaul Lya552fe22024-11-15 10:20:28 +00001136#ifndef HANDLE_MISSING_OBJECT_PATHS
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001137 break;
Chaul Lya552fe22024-11-15 10:20:28 +00001138#else
1139 // When a sensor in the MissingIsAcceptable list is NOT
1140 // on DBus and it still reaches here, which contradicts
1141 // to what we did in the Input sensor building step.
1142 // Continue.
1143 continue;
1144#endif
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001145 }
1146
1147 for (const auto& sensorPathIfacePair : sensorPathIfacePairs)
1148 {
1149 std::string shortName =
1150 getSensorNameFromPath(sensorPathIfacePair.first);
1151
1152 missingAcceptableSensors.push_back(shortName);
1153 }
1154 }
1155
ykchiu7c6d35d2023-05-10 17:01:46 +08001156 conf::ControllerInfo& info = conf[pidName];
Josh Lehan31058fd2023-01-13 11:06:16 -08001157
1158 std::vector<double> inputTempToMargin;
1159
1160 auto findTempToMargin = base.find("TempToMargin");
1161 if (findTempToMargin != base.end())
1162 {
1163 inputTempToMargin =
1164 std::get<std::vector<double>>(findTempToMargin->second);
1165 }
1166
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001167 info.inputs = spliceInputs(inputs, inputTempToMargin,
1168 missingAcceptableSensors);
James Feist50fdfe32018-09-24 15:51:09 -07001169
James Feist22c257a2018-08-31 14:07:12 -07001170 info.type = "stepwise";
1171 info.stepwiseInfo.ts = 1.0; // currently unused
James Feist3dfaafd2018-09-20 15:46:58 -07001172 info.stepwiseInfo.positiveHysteresis = 0.0;
1173 info.stepwiseInfo.negativeHysteresis = 0.0;
James Feist608304d2019-02-25 10:01:42 -08001174 std::string subtype = std::get<std::string>(base.at("Class"));
1175
1176 info.stepwiseInfo.isCeiling = (subtype == "Ceiling");
James Feist3dfaafd2018-09-20 15:46:58 -07001177 auto findPosHyst = base.find("PositiveHysteresis");
1178 auto findNegHyst = base.find("NegativeHysteresis");
1179 if (findPosHyst != base.end())
1180 {
James Feist1f802f52019-02-08 13:51:43 -08001181 info.stepwiseInfo.positiveHysteresis = std::visit(
James Feist208abce2018-12-06 09:59:10 -08001182 VariantToDoubleVisitor(), findPosHyst->second);
James Feist3dfaafd2018-09-20 15:46:58 -07001183 }
1184 if (findNegHyst != base.end())
1185 {
James Feist5782ab82019-04-02 08:38:48 -07001186 info.stepwiseInfo.negativeHysteresis = std::visit(
James Feist208abce2018-12-06 09:59:10 -08001187 VariantToDoubleVisitor(), findNegHyst->second);
James Feist3dfaafd2018-09-20 15:46:58 -07001188 }
James Feist22c257a2018-08-31 14:07:12 -07001189 std::vector<double> readings =
James Feist1f802f52019-02-08 13:51:43 -08001190 std::get<std::vector<double>>(base.at("Reading"));
James Feist22c257a2018-08-31 14:07:12 -07001191 if (readings.size() > ec::maxStepwisePoints)
1192 {
1193 throw std::invalid_argument("Too many stepwise points.");
1194 }
1195 if (readings.empty())
1196 {
1197 throw std::invalid_argument(
1198 "Must have one stepwise point.");
1199 }
1200 std::copy(readings.begin(), readings.end(),
1201 info.stepwiseInfo.reading);
1202 if (readings.size() < ec::maxStepwisePoints)
1203 {
1204 info.stepwiseInfo.reading[readings.size()] =
Patrick Venture5f59c0f2018-11-11 12:55:14 -08001205 std::numeric_limits<double>::quiet_NaN();
James Feist22c257a2018-08-31 14:07:12 -07001206 }
1207 std::vector<double> outputs =
James Feist1f802f52019-02-08 13:51:43 -08001208 std::get<std::vector<double>>(base.at("Output"));
James Feist22c257a2018-08-31 14:07:12 -07001209 if (readings.size() != outputs.size())
1210 {
1211 throw std::invalid_argument(
1212 "Outputs size must match readings");
1213 }
1214 std::copy(outputs.begin(), outputs.end(),
1215 info.stepwiseInfo.output);
1216 if (outputs.size() < ec::maxStepwisePoints)
1217 {
1218 info.stepwiseInfo.output[outputs.size()] =
Patrick Venture5f59c0f2018-11-11 12:55:14 -08001219 std::numeric_limits<double>::quiet_NaN();
James Feist22c257a2018-08-31 14:07:12 -07001220 }
James Feist7136a5a2018-07-19 09:52:05 -07001221 }
1222 }
1223 }
Patrick Venture39199b42020-10-08 14:40:29 -07001224 if constexpr (pid_control::conf::DEBUG)
James Feist7136a5a2018-07-19 09:52:05 -07001225 {
Patrick Venture39199b42020-10-08 14:40:29 -07001226 debugPrint(sensorConfig, zoneConfig, zoneDetailsConfig);
James Feist7136a5a2018-07-19 09:52:05 -07001227 }
James Feistc959c422018-11-01 12:33:40 -07001228 if (zoneConfig.empty() || zoneDetailsConfig.empty())
James Feist50fdfe32018-09-24 15:51:09 -07001229 {
James Feist1fe08952019-05-07 09:17:16 -07001230 std::cerr
1231 << "No fan zones, application pausing until new configuration\n";
1232 return false;
James Feist50fdfe32018-09-24 15:51:09 -07001233 }
James Feist1fe08952019-05-07 09:17:16 -07001234 return true;
James Feist7136a5a2018-07-19 09:52:05 -07001235}
Patrick Venturea0764872020-08-08 07:48:43 -07001236
James Feist7136a5a2018-07-19 09:52:05 -07001237} // namespace dbus_configuration
Patrick Venturea0764872020-08-08 07:48:43 -07001238} // namespace pid_control