blob: aa23a9698a3ac008f306e8813f4aec77c8fe2024 [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>
Alexander Hansenbd2c98e2025-11-12 12:47:00 +010035#include <xyz/openbmc_project/Control/FanPwm/client.hpp>
Alexander Hansend7665432025-11-12 14:01:48 +010036#include <xyz/openbmc_project/Control/ThermalMode/common.hpp>
Alexander Hansen1ec3d132025-10-27 17:19:50 +010037#include <xyz/openbmc_project/ObjectMapper/common.hpp>
Alexander Hansendae4a3a2025-11-11 17:12:01 +010038#include <xyz/openbmc_project/Sensor/Value/client.hpp>
Patrick Venturea83a3ec2020-08-04 09:52:05 -070039
40#include <algorithm>
Ed Tanousf8b6e552025-06-27 13:27:50 -070041#include <array>
James Feist64f072a2018-08-10 16:39:24 -070042#include <chrono>
Ed Tanousf8b6e552025-06-27 13:27:50 -070043#include <cstdint>
44#include <format>
James Feist7136a5a2018-07-19 09:52:05 -070045#include <iostream>
Ed Tanousf8b6e552025-06-27 13:27:50 -070046#include <limits>
James Feist1fe08952019-05-07 09:17:16 -070047#include <list>
Ed Tanousf8b6e552025-06-27 13:27:50 -070048#include <map>
49#include <stdexcept>
50#include <string>
51#include <tuple>
James Feist7136a5a2018-07-19 09:52:05 -070052#include <unordered_map>
Ed Tanousf8b6e552025-06-27 13:27:50 -070053#include <utility>
James Feist1f802f52019-02-08 13:51:43 -080054#include <variant>
Ed Tanousf8b6e552025-06-27 13:27:50 -070055#include <vector>
James Feist7136a5a2018-07-19 09:52:05 -070056
Alexander Hansen1ec3d132025-10-27 17:19:50 +010057using ObjectMapper = sdbusplus::common::xyz::openbmc_project::ObjectMapper;
Alexander Hansendae4a3a2025-11-11 17:12:01 +010058using SensorValue = sdbusplus::common::xyz::openbmc_project::sensor::Value;
Alexander Hansenbd2c98e2025-11-12 12:47:00 +010059using ControlFanPwm = sdbusplus::common::xyz::openbmc_project::control::FanPwm;
Alexander Hansend7665432025-11-12 14:01:48 +010060using ControlThermalMode =
61 sdbusplus::common::xyz::openbmc_project::control::ThermalMode;
Alexander Hansen1ec3d132025-10-27 17:19:50 +010062
Patrick Venturea0764872020-08-08 07:48:43 -070063namespace pid_control
64{
65
Patrick Venturee2ec0f62018-09-04 12:30:27 -070066constexpr const char* pidConfigurationInterface =
James Feist7136a5a2018-07-19 09:52:05 -070067 "xyz.openbmc_project.Configuration.Pid";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070068constexpr const char* objectManagerInterface =
James Feist7136a5a2018-07-19 09:52:05 -070069 "org.freedesktop.DBus.ObjectManager";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070070constexpr const char* pidZoneConfigurationInterface =
James Feist7136a5a2018-07-19 09:52:05 -070071 "xyz.openbmc_project.Configuration.Pid.Zone";
James Feist22c257a2018-08-31 14:07:12 -070072constexpr const char* stepwiseConfigurationInterface =
73 "xyz.openbmc_project.Configuration.Stepwise";
James Feist7136a5a2018-07-19 09:52:05 -070074
James Feist991ebd82020-07-21 11:14:52 -070075using Association = std::tuple<std::string, std::string, std::string>;
76using Associations = std::vector<Association>;
77
James Feist5ec20272019-07-10 11:59:57 -070078namespace thresholds
79{
80constexpr const char* warningInterface =
81 "xyz.openbmc_project.Sensor.Threshold.Warning";
82constexpr const char* criticalInterface =
83 "xyz.openbmc_project.Sensor.Threshold.Critical";
84const std::array<const char*, 4> types = {"CriticalLow", "CriticalHigh",
85 "WarningLow", "WarningHigh"};
86
87} // namespace thresholds
88
James Feist7136a5a2018-07-19 09:52:05 -070089namespace dbus_configuration
90{
Jason Lingf3b04fd2020-07-24 09:33:04 -070091using SensorInterfaceType = std::pair<std::string, std::string>;
92
93inline std::string getSensorNameFromPath(const std::string& dbusPath)
94{
Ed Tanousd2768c52025-06-26 11:42:57 -070095 return dbusPath.substr(dbusPath.find_last_of('/') + 1);
Jason Lingf3b04fd2020-07-24 09:33:04 -070096}
97
98inline std::string sensorNameToDbusName(const std::string& sensorName)
99{
100 std::string retString = sensorName;
101 std::replace(retString.begin(), retString.end(), ' ', '_');
102 return retString;
103}
James Feist5ec20272019-07-10 11:59:57 -0700104
Patrick Williamsb228bc32022-07-22 19:26:56 -0500105std::vector<std::string> getSelectedProfiles(sdbusplus::bus_t& bus)
James Feistf0096a02019-02-21 11:25:22 -0800106{
107 std::vector<std::string> ret;
Alexander Hansen1ec3d132025-10-27 17:19:50 +0100108 auto mapper = bus.new_method_call(
109 ObjectMapper::default_service, ObjectMapper::instance_path,
110 ObjectMapper::interface, ObjectMapper::method_names::get_sub_tree);
Alexander Hansend7665432025-11-12 14:01:48 +0100111 mapper.append("/", 0,
112 std::array<const char*, 1>{ControlThermalMode::interface});
James Feistf0096a02019-02-21 11:25:22 -0800113 std::unordered_map<
114 std::string, std::unordered_map<std::string, std::vector<std::string>>>
115 respData;
116
117 try
118 {
119 auto resp = bus.call(mapper);
120 resp.read(respData);
121 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500122 catch (const sdbusplus::exception_t&)
James Feistf0096a02019-02-21 11:25:22 -0800123 {
124 // can't do anything without mapper call data
125 throw std::runtime_error("ObjectMapper Call Failure");
126 }
127 if (respData.empty())
128 {
129 // if the user has profiles but doesn't expose the interface to select
130 // one, just go ahead without using profiles
131 return ret;
132 }
133
134 // assumption is that we should only have a small handful of selected
135 // profiles at a time (probably only 1), so calling each individually should
136 // not incur a large cost
137 for (const auto& objectPair : respData)
138 {
139 const std::string& path = objectPair.first;
140 for (const auto& ownerPair : objectPair.second)
141 {
142 const std::string& busName = ownerPair.first;
143 auto getProfile =
144 bus.new_method_call(busName.c_str(), path.c_str(),
145 "org.freedesktop.DBus.Properties", "Get");
Alexander Hansend7665432025-11-12 14:01:48 +0100146 getProfile.append(ControlThermalMode::interface,
147 ControlThermalMode::property_names::current);
James Feistf0096a02019-02-21 11:25:22 -0800148 std::variant<std::string> variantResp;
149 try
150 {
151 auto resp = bus.call(getProfile);
152 resp.read(variantResp);
153 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500154 catch (const sdbusplus::exception_t&)
James Feistf0096a02019-02-21 11:25:22 -0800155 {
156 throw std::runtime_error("Failure getting profile");
157 }
158 std::string mode = std::get<std::string>(variantResp);
159 ret.emplace_back(std::move(mode));
160 }
161 }
Patrick Venture39199b42020-10-08 14:40:29 -0700162 if constexpr (pid_control::conf::DEBUG)
James Feistf0096a02019-02-21 11:25:22 -0800163 {
164 std::cout << "Profiles selected: ";
165 for (const auto& profile : ret)
166 {
167 std::cout << profile << " ";
168 }
169 std::cout << "\n";
170 }
171 return ret;
172}
173
James Feist991ebd82020-07-21 11:14:52 -0700174int eventHandler(sd_bus_message* m, void* context, sd_bus_error*)
James Feist7136a5a2018-07-19 09:52:05 -0700175{
James Feist991ebd82020-07-21 11:14:52 -0700176 if (context == nullptr || m == nullptr)
James Feist1fe08952019-05-07 09:17:16 -0700177 {
178 throw std::runtime_error("Invalid match");
179 }
James Feist991ebd82020-07-21 11:14:52 -0700180
181 // we skip associations because the mapper populates these, not the sensors
Josh Lehan10e46ef2023-02-01 18:25:58 -0800182 const std::array<const char*, 2> skipList = {
183 "xyz.openbmc_project.Association",
184 "xyz.openbmc_project.Association.Definitions"};
James Feist991ebd82020-07-21 11:14:52 -0700185
Patrick Williamsb228bc32022-07-22 19:26:56 -0500186 sdbusplus::message_t message(m);
James Feist991ebd82020-07-21 11:14:52 -0700187 if (std::string(message.get_member()) == "InterfacesAdded")
188 {
189 sdbusplus::message::object_path path;
190 std::unordered_map<
191 std::string,
192 std::unordered_map<std::string, std::variant<Associations, bool>>>
193 data;
194
195 message.read(path, data);
196
197 for (const char* skip : skipList)
198 {
199 auto find = data.find(skip);
200 if (find != data.end())
201 {
202 data.erase(find);
203 if (data.empty())
204 {
205 return 1;
206 }
207 }
208 }
Josh Lehan10e46ef2023-02-01 18:25:58 -0800209
210 if constexpr (pid_control::conf::DEBUG)
211 {
212 std::cout << "New config detected: " << path.str << std::endl;
213 for (auto& d : data)
214 {
215 std::cout << "\tdata is " << d.first << std::endl;
216 for (auto& second : d.second)
217 {
218 std::cout << "\t\tdata is " << second.first << std::endl;
219 }
220 }
221 }
James Feist991ebd82020-07-21 11:14:52 -0700222 }
223
James Feist1fe08952019-05-07 09:17:16 -0700224 boost::asio::steady_timer* timer =
225 static_cast<boost::asio::steady_timer*>(context);
226
227 // do a brief sleep as we tend to get a bunch of these events at
228 // once
229 timer->expires_after(std::chrono::seconds(2));
230 timer->async_wait([](const boost::system::error_code ec) {
231 if (ec == boost::asio::error::operation_aborted)
232 {
233 /* another timer started*/
234 return;
235 }
236
237 std::cout << "New configuration detected, reloading\n.";
Yong Li298a95c2020-04-07 15:11:02 +0800238 tryRestartControlLoops();
James Feist1fe08952019-05-07 09:17:16 -0700239 });
240
241 return 1;
242}
243
Patrick Williamsb228bc32022-07-22 19:26:56 -0500244void createMatches(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer)
James Feist1fe08952019-05-07 09:17:16 -0700245{
246 // this is a list because the matches can't be moved
Patrick Williamsb228bc32022-07-22 19:26:56 -0500247 static std::list<sdbusplus::bus::match_t> matches;
James Feist1fe08952019-05-07 09:17:16 -0700248
James Feist3987c8b2019-05-13 10:43:17 -0700249 const std::array<std::string, 4> interfaces = {
Alexander Hansend7665432025-11-12 14:01:48 +0100250 ControlThermalMode::interface, pidConfigurationInterface,
James Feist3987c8b2019-05-13 10:43:17 -0700251 pidZoneConfigurationInterface, stepwiseConfigurationInterface};
James Feist1fe08952019-05-07 09:17:16 -0700252
253 // this list only needs to be created once
254 if (!matches.empty())
255 {
256 return;
257 }
258
259 // we restart when the configuration changes or there are new sensors
260 for (const auto& interface : interfaces)
261 {
262 matches.emplace_back(
263 bus,
264 "type='signal',member='PropertiesChanged',arg0namespace='" +
265 interface + "'",
266 eventHandler, &timer);
267 }
268 matches.emplace_back(
269 bus,
270 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
271 "sensors/'",
272 eventHandler, &timer);
Jinliang Wangc2a311b2023-04-26 18:36:56 +0000273 matches.emplace_back(bus,
274 "type='signal',member='InterfacesRemoved',arg0path='/"
275 "xyz/openbmc_project/sensors/'",
276 eventHandler, &timer);
James Feist1fe08952019-05-07 09:17:16 -0700277}
278
Jason Ling6fc301f2020-07-23 12:39:57 -0700279/**
280 * retrieve an attribute from the pid configuration map
281 * @param[in] base - the PID configuration map, keys are the attributes and
282 * value is the variant associated with that attribute.
283 * @param attributeName - the name of the attribute
284 * @return a variant holding the value associated with a key
285 * @throw runtime_error : attributeName is not in base
286 */
287inline DbusVariantType getPIDAttribute(
288 const std::unordered_map<std::string, DbusVariantType>& base,
289 const std::string& attributeName)
290{
291 auto search = base.find(attributeName);
292 if (search == base.end())
293 {
294 throw std::runtime_error("missing attribute " + attributeName);
295 }
296 return search->second;
297}
298
Harvey Wu239aa7d2022-11-18 08:43:34 +0800299inline void getCycleTimeSetting(
300 const std::unordered_map<std::string, DbusVariantType>& zone,
301 const int zoneIndex, const std::string& attributeName, uint64_t& value)
302{
303 auto findAttributeName = zone.find(attributeName);
304 if (findAttributeName != zone.end())
305 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400306 double tmpAttributeValue =
307 std::visit(VariantToDoubleVisitor(), zone.at(attributeName));
Harvey Wu239aa7d2022-11-18 08:43:34 +0800308 if (tmpAttributeValue >= 1.0)
309 {
310 value = static_cast<uint64_t>(tmpAttributeValue);
311 }
312 else
313 {
314 std::cerr << "Zone " << zoneIndex << ": " << attributeName
315 << " is invalid. Use default " << value << " ms\n";
316 }
317 }
318 else
319 {
320 std::cerr << "Zone " << zoneIndex << ": " << attributeName
321 << " cannot find setting. Use default " << value << " ms\n";
322 }
323}
324
James Feist5ec20272019-07-10 11:59:57 -0700325void populatePidInfo(
Patrick Williamscd1e78a2025-04-07 17:21:05 -0400326 sdbusplus::bus_t& bus,
James Feist5ec20272019-07-10 11:59:57 -0700327 const std::unordered_map<std::string, DbusVariantType>& base,
Patrick Venture1df9e872020-10-08 15:35:01 -0700328 conf::ControllerInfo& info, const std::string* thresholdProperty,
Patrick Venture73823182020-10-08 15:12:51 -0700329 const std::map<std::string, conf::SensorConfig>& sensorConfig)
James Feist5ec20272019-07-10 11:59:57 -0700330{
Jason Ling6fc301f2020-07-23 12:39:57 -0700331 info.type = std::get<std::string>(getPIDAttribute(base, "Class"));
James Feist5ec20272019-07-10 11:59:57 -0700332 if (info.type == "fan")
333 {
334 info.setpoint = 0;
335 }
336 else
337 {
Jason Ling6fc301f2020-07-23 12:39:57 -0700338 info.setpoint = std::visit(VariantToDoubleVisitor(),
339 getPIDAttribute(base, "SetPoint"));
James Feist5ec20272019-07-10 11:59:57 -0700340 }
341
ykchiu9fe3a3c2023-05-11 13:43:54 +0800342 int failsafepercent = 0;
343 auto findFailSafe = base.find("FailSafePercent");
344 if (findFailSafe != base.end())
345 {
346 failsafepercent = std::visit(VariantToDoubleVisitor(),
347 getPIDAttribute(base, "FailSafePercent"));
348 }
349 info.failSafePercent = failsafepercent;
350
James Feist5ec20272019-07-10 11:59:57 -0700351 if (thresholdProperty != nullptr)
352 {
353 std::string interface;
354 if (*thresholdProperty == "WarningHigh" ||
355 *thresholdProperty == "WarningLow")
356 {
357 interface = thresholds::warningInterface;
358 }
359 else
360 {
361 interface = thresholds::criticalInterface;
362 }
Josh Lehan31058fd2023-01-13 11:06:16 -0800363
364 // Although this checks only the first vector element for the
365 // named threshold, it is OK, because the SetPointOffset parser
366 // splits up the input into individual vectors, each with only a
367 // single element, if it detects that SetPointOffset is in use.
368 const std::string& path =
369 sensorConfig.at(info.inputs.front().name).readPath;
James Feist5ec20272019-07-10 11:59:57 -0700370
Patrick Williamscd1e78a2025-04-07 17:21:05 -0400371 DbusHelper helper(bus);
Patrick Venture9b936922020-08-10 11:28:39 -0700372 std::string service = helper.getService(interface, path);
James Feist5ec20272019-07-10 11:59:57 -0700373 double reading = 0;
374 try
375 {
Patrick Venture9b936922020-08-10 11:28:39 -0700376 helper.getProperty(service, path, interface, *thresholdProperty,
377 reading);
James Feist5ec20272019-07-10 11:59:57 -0700378 }
Patrick Williamsb228bc32022-07-22 19:26:56 -0500379 catch (const sdbusplus::exception_t& ex)
James Feist5ec20272019-07-10 11:59:57 -0700380 {
381 // unsupported threshold, leaving reading at 0
382 }
383
384 info.setpoint += reading;
385 }
386
387 info.pidInfo.ts = 1.0; // currently unused
Jason Ling6fc301f2020-07-23 12:39:57 -0700388 info.pidInfo.proportionalCoeff = std::visit(
389 VariantToDoubleVisitor(), getPIDAttribute(base, "PCoefficient"));
390 info.pidInfo.integralCoeff = std::visit(
391 VariantToDoubleVisitor(), getPIDAttribute(base, "ICoefficient"));
Josh Lehanc612c052022-12-12 09:56:47 -0800392 // DCoefficient is below, it is optional, same reason as in buildjson.cpp
Jason Ling6fc301f2020-07-23 12:39:57 -0700393 info.pidInfo.feedFwdOffset = std::visit(
394 VariantToDoubleVisitor(), getPIDAttribute(base, "FFOffCoefficient"));
395 info.pidInfo.feedFwdGain = std::visit(
396 VariantToDoubleVisitor(), getPIDAttribute(base, "FFGainCoefficient"));
397 info.pidInfo.integralLimit.max = std::visit(
398 VariantToDoubleVisitor(), getPIDAttribute(base, "ILimitMax"));
399 info.pidInfo.integralLimit.min = std::visit(
400 VariantToDoubleVisitor(), getPIDAttribute(base, "ILimitMin"));
401 info.pidInfo.outLim.max = std::visit(VariantToDoubleVisitor(),
402 getPIDAttribute(base, "OutLimitMax"));
403 info.pidInfo.outLim.min = std::visit(VariantToDoubleVisitor(),
404 getPIDAttribute(base, "OutLimitMin"));
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400405 info.pidInfo.slewNeg =
406 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewNeg"));
407 info.pidInfo.slewPos =
408 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewPos"));
Josh Lehanc612c052022-12-12 09:56:47 -0800409
Delphine CC Chiu97889632023-11-06 11:32:46 +0800410 bool checkHysterWithSetpt = false;
James Feist5ec20272019-07-10 11:59:57 -0700411 double negativeHysteresis = 0;
412 double positiveHysteresis = 0;
Josh Lehanc612c052022-12-12 09:56:47 -0800413 double derivativeCoeff = 0;
James Feist5ec20272019-07-10 11:59:57 -0700414
Delphine CC Chiu5d897e22024-06-04 13:33:22 +0800415 auto findCheckHysterFlag = base.find("CheckHysteresisWithSetpoint");
James Feist5ec20272019-07-10 11:59:57 -0700416 auto findNeg = base.find("NegativeHysteresis");
417 auto findPos = base.find("PositiveHysteresis");
Josh Lehanc612c052022-12-12 09:56:47 -0800418 auto findDerivative = base.find("DCoefficient");
James Feist5ec20272019-07-10 11:59:57 -0700419
Delphine CC Chiu97889632023-11-06 11:32:46 +0800420 if (findCheckHysterFlag != base.end())
421 {
422 checkHysterWithSetpt = std::get<bool>(findCheckHysterFlag->second);
423 }
James Feist5ec20272019-07-10 11:59:57 -0700424 if (findNeg != base.end())
425 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400426 negativeHysteresis =
427 std::visit(VariantToDoubleVisitor(), findNeg->second);
James Feist5ec20272019-07-10 11:59:57 -0700428 }
James Feist5ec20272019-07-10 11:59:57 -0700429 if (findPos != base.end())
430 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400431 positiveHysteresis =
432 std::visit(VariantToDoubleVisitor(), findPos->second);
James Feist5ec20272019-07-10 11:59:57 -0700433 }
Josh Lehanc612c052022-12-12 09:56:47 -0800434 if (findDerivative != base.end())
435 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400436 derivativeCoeff =
437 std::visit(VariantToDoubleVisitor(), findDerivative->second);
Josh Lehanc612c052022-12-12 09:56:47 -0800438 }
439
Delphine CC Chiu97889632023-11-06 11:32:46 +0800440 info.pidInfo.checkHysterWithSetpt = checkHysterWithSetpt;
James Feist5ec20272019-07-10 11:59:57 -0700441 info.pidInfo.negativeHysteresis = negativeHysteresis;
442 info.pidInfo.positiveHysteresis = positiveHysteresis;
Josh Lehanc612c052022-12-12 09:56:47 -0800443 info.pidInfo.derivativeCoeff = derivativeCoeff;
James Feist5ec20272019-07-10 11:59:57 -0700444}
445
Patrick Williamsb228bc32022-07-22 19:26:56 -0500446bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
Patrick Venture73823182020-10-08 15:12:51 -0700447 std::map<std::string, conf::SensorConfig>& sensorConfig,
448 std::map<int64_t, conf::PIDConf>& zoneConfig,
449 std::map<int64_t, conf::ZoneConfig>& zoneDetailsConfig)
James Feist1fe08952019-05-07 09:17:16 -0700450{
James Feist1fe08952019-05-07 09:17:16 -0700451 sensorConfig.clear();
452 zoneConfig.clear();
453 zoneDetailsConfig.clear();
454
455 createMatches(bus, timer);
456
Alexander Hansen1ec3d132025-10-27 17:19:50 +0100457 auto mapper = bus.new_method_call(
458 ObjectMapper::default_service, ObjectMapper::instance_path,
459 ObjectMapper::interface, ObjectMapper::method_names::get_sub_tree);
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400460 mapper.append(
461 "/", 0,
462 std::array<const char*, 6>{
463 objectManagerInterface, pidConfigurationInterface,
464 pidZoneConfigurationInterface, stepwiseConfigurationInterface,
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100465 SensorValue::interface, ControlFanPwm::interface});
James Feist7136a5a2018-07-19 09:52:05 -0700466 std::unordered_map<
467 std::string, std::unordered_map<std::string, std::vector<std::string>>>
468 respData;
James Feist22c257a2018-08-31 14:07:12 -0700469 try
470 {
471 auto resp = bus.call(mapper);
James Feist22c257a2018-08-31 14:07:12 -0700472 resp.read(respData);
473 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500474 catch (const sdbusplus::exception_t&)
James Feist22c257a2018-08-31 14:07:12 -0700475 {
476 // can't do anything without mapper call data
477 throw std::runtime_error("ObjectMapper Call Failure");
478 }
James Feist7136a5a2018-07-19 09:52:05 -0700479
James Feist7136a5a2018-07-19 09:52:05 -0700480 if (respData.empty())
481 {
James Feist22c257a2018-08-31 14:07:12 -0700482 // can't do anything without mapper call data
James Feist7136a5a2018-07-19 09:52:05 -0700483 throw std::runtime_error("No configuration data available from Mapper");
484 }
485 // create a map of pair of <has pid configuration, ObjectManager path>
486 std::unordered_map<std::string, std::pair<bool, std::string>> owners;
487 // and a map of <path, interface> for sensors
488 std::unordered_map<std::string, std::string> sensors;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700489 for (const auto& objectPair : respData)
James Feist7136a5a2018-07-19 09:52:05 -0700490 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700491 for (const auto& ownerPair : objectPair.second)
James Feist7136a5a2018-07-19 09:52:05 -0700492 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700493 auto& owner = owners[ownerPair.first];
494 for (const std::string& interface : ownerPair.second)
James Feist7136a5a2018-07-19 09:52:05 -0700495 {
James Feist7136a5a2018-07-19 09:52:05 -0700496 if (interface == objectManagerInterface)
497 {
498 owner.second = objectPair.first;
499 }
500 if (interface == pidConfigurationInterface ||
James Feist22c257a2018-08-31 14:07:12 -0700501 interface == pidZoneConfigurationInterface ||
502 interface == stepwiseConfigurationInterface)
James Feist7136a5a2018-07-19 09:52:05 -0700503 {
504 owner.first = true;
505 }
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100506 if (interface == SensorValue::interface ||
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100507 interface == ControlFanPwm::interface)
James Feist7136a5a2018-07-19 09:52:05 -0700508 {
509 // we're not interested in pwm sensors, just pwm control
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100510 if (interface == SensorValue::interface &&
James Feist7136a5a2018-07-19 09:52:05 -0700511 objectPair.first.find("pwm") != std::string::npos)
512 {
513 continue;
514 }
515 sensors[objectPair.first] = interface;
516 }
517 }
518 }
519 }
520 ManagedObjectType configurations;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700521 for (const auto& owner : owners)
James Feist7136a5a2018-07-19 09:52:05 -0700522 {
523 // skip if no pid configuration (means probably a sensor)
524 if (!owner.second.first)
525 {
526 continue;
527 }
528 auto endpoint = bus.new_method_call(
529 owner.first.c_str(), owner.second.second.c_str(),
530 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
James Feist22c257a2018-08-31 14:07:12 -0700531 ManagedObjectType configuration;
532 try
James Feist7136a5a2018-07-19 09:52:05 -0700533 {
Manojkiran Eda7ca88872024-06-17 11:55:48 +0530534 auto response = bus.call(endpoint);
535 response.read(configuration);
James Feist22c257a2018-08-31 14:07:12 -0700536 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500537 catch (const sdbusplus::exception_t&)
James Feist22c257a2018-08-31 14:07:12 -0700538 {
539 // this shouldn't happen, probably means daemon crashed
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400540 throw std::runtime_error(
541 "Error getting managed objects from " + owner.first);
James Feist7136a5a2018-07-19 09:52:05 -0700542 }
James Feist22c257a2018-08-31 14:07:12 -0700543
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700544 for (auto& pathPair : configuration)
James Feist7136a5a2018-07-19 09:52:05 -0700545 {
546 if (pathPair.second.find(pidConfigurationInterface) !=
547 pathPair.second.end() ||
548 pathPair.second.find(pidZoneConfigurationInterface) !=
James Feist22c257a2018-08-31 14:07:12 -0700549 pathPair.second.end() ||
550 pathPair.second.find(stepwiseConfigurationInterface) !=
James Feist7136a5a2018-07-19 09:52:05 -0700551 pathPair.second.end())
552 {
553 configurations.emplace(pathPair);
554 }
James Feistf0096a02019-02-21 11:25:22 -0800555 }
556 }
557
558 // remove controllers from config that aren't in the current profile(s)
James Feist3987c8b2019-05-13 10:43:17 -0700559 std::vector<std::string> selectedProfiles = getSelectedProfiles(bus);
560 if (selectedProfiles.size())
James Feistf0096a02019-02-21 11:25:22 -0800561 {
James Feist3987c8b2019-05-13 10:43:17 -0700562 for (auto pathIt = configurations.begin();
563 pathIt != configurations.end();)
James Feistf0096a02019-02-21 11:25:22 -0800564 {
James Feist3987c8b2019-05-13 10:43:17 -0700565 for (auto confIt = pathIt->second.begin();
566 confIt != pathIt->second.end();)
James Feistf0096a02019-02-21 11:25:22 -0800567 {
James Feist3987c8b2019-05-13 10:43:17 -0700568 auto profilesFind = confIt->second.find("Profiles");
569 if (profilesFind == confIt->second.end())
James Feistf0096a02019-02-21 11:25:22 -0800570 {
James Feist3987c8b2019-05-13 10:43:17 -0700571 confIt++;
572 continue; // if no profiles selected, apply always
573 }
574 auto profiles =
575 std::get<std::vector<std::string>>(profilesFind->second);
576 if (profiles.empty())
577 {
578 confIt++;
579 continue;
580 }
581
582 bool found = false;
583 for (const std::string& profile : profiles)
584 {
585 if (std::find(selectedProfiles.begin(),
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400586 selectedProfiles.end(), profile) !=
587 selectedProfiles.end())
James Feist3987c8b2019-05-13 10:43:17 -0700588 {
589 found = true;
590 break;
591 }
592 }
593 if (found)
594 {
595 confIt++;
James Feistf0096a02019-02-21 11:25:22 -0800596 }
597 else
598 {
James Feist3987c8b2019-05-13 10:43:17 -0700599 confIt = pathIt->second.erase(confIt);
James Feistf0096a02019-02-21 11:25:22 -0800600 }
601 }
James Feist3987c8b2019-05-13 10:43:17 -0700602 if (pathIt->second.empty())
James Feistf0096a02019-02-21 11:25:22 -0800603 {
James Feist3987c8b2019-05-13 10:43:17 -0700604 pathIt = configurations.erase(pathIt);
James Feistf0096a02019-02-21 11:25:22 -0800605 }
James Feist3987c8b2019-05-13 10:43:17 -0700606 else
James Feistf0096a02019-02-21 11:25:22 -0800607 {
James Feist3987c8b2019-05-13 10:43:17 -0700608 pathIt++;
James Feistf0096a02019-02-21 11:25:22 -0800609 }
James Feist7136a5a2018-07-19 09:52:05 -0700610 }
611 }
James Feist8c3c51e2018-08-08 16:31:43 -0700612
Josh Lehan998fbe62020-09-20 21:21:05 -0700613 // On D-Bus, although not necessary,
614 // having the "zoneID" field can still be useful,
615 // as it is used for diagnostic messages,
616 // logging file names, and so on.
617 // Accept optional "ZoneIndex" parameter to explicitly specify.
618 // If not present, or not unique, auto-assign index,
619 // using 0-based numbering, ensuring uniqueness.
620 std::map<std::string, int64_t> foundZones;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700621 for (const auto& configuration : configurations)
James Feist7136a5a2018-07-19 09:52:05 -0700622 {
623 auto findZone =
624 configuration.second.find(pidZoneConfigurationInterface);
625 if (findZone != configuration.second.end())
626 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700627 const auto& zone = findZone->second;
James Feistffd418b2018-11-15 14:46:36 -0800628
James Feist1f802f52019-02-08 13:51:43 -0800629 const std::string& name = std::get<std::string>(zone.at("Name"));
Josh Lehan998fbe62020-09-20 21:21:05 -0700630
631 auto findZoneIndex = zone.find("ZoneIndex");
632 if (findZoneIndex == zone.end())
633 {
634 continue;
635 }
636
637 auto ptrZoneIndex = std::get_if<double>(&(findZoneIndex->second));
638 if (!ptrZoneIndex)
639 {
640 continue;
641 }
642
643 auto desiredIndex = static_cast<int64_t>(*ptrZoneIndex);
644 auto grantedIndex = setZoneIndex(name, foundZones, desiredIndex);
645 std::cout << "Zone " << name << " is at ZoneIndex " << grantedIndex
646 << "\n";
647 }
648 }
649
650 for (const auto& configuration : configurations)
651 {
652 auto findZone =
653 configuration.second.find(pidZoneConfigurationInterface);
654 if (findZone != configuration.second.end())
655 {
656 const auto& zone = findZone->second;
657
658 const std::string& name = std::get<std::string>(zone.at("Name"));
659
660 auto index = getZoneIndex(name, foundZones);
James Feist8c3c51e2018-08-08 16:31:43 -0700661
Patrick Venturec54fbd82018-10-30 19:40:05 -0700662 auto& details = zoneDetailsConfig[index];
Josh Lehan998fbe62020-09-20 21:21:05 -0700663
James Feist3484bed2019-02-25 13:28:18 -0800664 details.minThermalOutput = std::visit(VariantToDoubleVisitor(),
665 zone.at("MinThermalOutput"));
ykchiu9fe3a3c2023-05-11 13:43:54 +0800666
667 int failsafepercent = 0;
668 auto findFailSafe = zone.find("FailSafePercent");
669 if (findFailSafe != zone.end())
670 {
671 failsafepercent = std::visit(VariantToDoubleVisitor(),
672 zone.at("FailSafePercent"));
673 }
674 details.failsafePercent = failsafepercent;
Josh Lehan9f9a06a2022-12-14 10:39:45 -0800675
Harvey Wu239aa7d2022-11-18 08:43:34 +0800676 getCycleTimeSetting(zone, index, "CycleIntervalTimeMS",
677 details.cycleTime.cycleIntervalTimeMS);
678 getCycleTimeSetting(zone, index, "UpdateThermalsTimeMS",
679 details.cycleTime.updateThermalsTimeMS);
Delphine CC Chiu97889632023-11-06 11:32:46 +0800680
681 bool accumulateSetPoint = false;
682 auto findAccSetPoint = zone.find("AccumulateSetPoint");
683 if (findAccSetPoint != zone.end())
684 {
685 accumulateSetPoint = std::get<bool>(findAccSetPoint->second);
686 }
687 details.accumulateSetPoint = accumulateSetPoint;
James Feist7136a5a2018-07-19 09:52:05 -0700688 }
689 auto findBase = configuration.second.find(pidConfigurationInterface);
Jason Lingf3b04fd2020-07-24 09:33:04 -0700690 // loop through all the PID configurations and fill out a sensor config
James Feist22c257a2018-08-31 14:07:12 -0700691 if (findBase != configuration.second.end())
James Feist7136a5a2018-07-19 09:52:05 -0700692 {
James Feist22c257a2018-08-31 14:07:12 -0700693 const auto& base =
694 configuration.second.at(pidConfigurationInterface);
ykchiu7c6d35d2023-05-10 17:01:46 +0800695 const std::string pidName =
696 sensorNameToDbusName(std::get<std::string>(base.at("Name")));
Jason Lingf3b04fd2020-07-24 09:33:04 -0700697 const std::string pidClass =
698 std::get<std::string>(base.at("Class"));
James Feist22c257a2018-08-31 14:07:12 -0700699 const std::vector<std::string>& zones =
James Feist1f802f52019-02-08 13:51:43 -0800700 std::get<std::vector<std::string>>(base.at("Zones"));
James Feist22c257a2018-08-31 14:07:12 -0700701 for (const std::string& zone : zones)
James Feist7136a5a2018-07-19 09:52:05 -0700702 {
Josh Lehan998fbe62020-09-20 21:21:05 -0700703 auto index = getZoneIndex(zone, foundZones);
704
James Feistf81f2882019-02-26 11:26:36 -0800705 conf::PIDConf& conf = zoneConfig[index];
Jason Lingf3b04fd2020-07-24 09:33:04 -0700706 std::vector<std::string> inputSensorNames(
707 std::get<std::vector<std::string>>(base.at("Inputs")));
708 std::vector<std::string> outputSensorNames;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800709 std::vector<std::string> missingAcceptableSensorNames;
Chaul Lya552fe22024-11-15 10:20:28 +0000710 std::vector<std::string> archivedInputSensorNames;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800711
712 auto findMissingAcceptable = base.find("MissingIsAcceptable");
713 if (findMissingAcceptable != base.end())
714 {
715 missingAcceptableSensorNames =
716 std::get<std::vector<std::string>>(
717 findMissingAcceptable->second);
718 }
James Feist50fdfe32018-09-24 15:51:09 -0700719
Jason Lingf3b04fd2020-07-24 09:33:04 -0700720 // assumption: all fan pids must have at least one output
721 if (pidClass == "fan")
James Feist50fdfe32018-09-24 15:51:09 -0700722 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700723 outputSensorNames = std::get<std::vector<std::string>>(
724 getPIDAttribute(base, "Outputs"));
James Feist50fdfe32018-09-24 15:51:09 -0700725 }
James Feist1738e2a2019-02-04 15:57:03 -0800726
Alex.Song8f73ad72021-10-07 00:18:27 +0800727 bool unavailableAsFailed = true;
728 auto findUnavailableAsFailed =
729 base.find("InputUnavailableAsFailed");
730 if (findUnavailableAsFailed != base.end())
731 {
732 unavailableAsFailed =
733 std::get<bool>(findUnavailableAsFailed->second);
734 }
735
Jason Lingf3b04fd2020-07-24 09:33:04 -0700736 std::vector<SensorInterfaceType> inputSensorInterfaces;
737 std::vector<SensorInterfaceType> outputSensorInterfaces;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800738 std::vector<SensorInterfaceType>
739 missingAcceptableSensorInterfaces;
740
Jason Lingf3b04fd2020-07-24 09:33:04 -0700741 /* populate an interface list for different sensor direction
742 * types (input,output)
743 */
744 /* take the Inputs from the configuration and generate
745 * a list of dbus descriptors (path, interface).
746 * Mapping can be many-to-one since an element of Inputs can be
747 * a regex
748 */
749 for (const std::string& sensorName : inputSensorNames)
James Feist50fdfe32018-09-24 15:51:09 -0700750 {
Chaul Lya552fe22024-11-15 10:20:28 +0000751#ifndef HANDLE_MISSING_OBJECT_PATHS
Jason Lingf3b04fd2020-07-24 09:33:04 -0700752 findSensors(sensors, sensorNameToDbusName(sensorName),
753 inputSensorInterfaces);
Chaul Lya552fe22024-11-15 10:20:28 +0000754#else
755 std::vector<std::pair<std::string, std::string>>
756 sensorPathIfacePairs;
757 auto found =
758 findSensors(sensors, sensorNameToDbusName(sensorName),
759 sensorPathIfacePairs);
760 if (found)
761 {
762 inputSensorInterfaces.insert(
763 inputSensorInterfaces.end(),
764 sensorPathIfacePairs.begin(),
765 sensorPathIfacePairs.end());
766 }
767 else if (pidClass != "fan")
768 {
769 if (std::find(missingAcceptableSensorNames.begin(),
770 missingAcceptableSensorNames.end(),
771 sensorName) ==
772 missingAcceptableSensorNames.end())
773 {
774 std::cerr
775 << "Pid controller: Missing a missing-unacceptable sensor from D-Bus "
776 << sensorName << "\n";
777 std::string inputSensorName =
778 sensorNameToDbusName(sensorName);
779 auto& config = sensorConfig[inputSensorName];
780 archivedInputSensorNames.push_back(inputSensorName);
781 config.type = pidClass;
782 config.readPath =
783 getSensorPath(config.type, inputSensorName);
784 config.timeout = 0;
785 config.ignoreDbusMinMax = true;
786 config.unavailableAsFailed = unavailableAsFailed;
787 }
788 else
789 {
790 // When an input sensor is NOT on DBus, and it's in
791 // the MissingIsAcceptable list. Ignore it and
792 // continue with the next input sensor.
793 std::cout
794 << "Pid controller: Missing a missing-acceptable sensor from D-Bus "
795 << sensorName << "\n";
796 continue;
797 }
798 }
799#endif
Jason Lingf3b04fd2020-07-24 09:33:04 -0700800 }
801 for (const std::string& sensorName : outputSensorNames)
802 {
803 findSensors(sensors, sensorNameToDbusName(sensorName),
804 outputSensorInterfaces);
James Feist1738e2a2019-02-04 15:57:03 -0800805 }
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800806 for (const std::string& sensorName :
807 missingAcceptableSensorNames)
808 {
809 findSensors(sensors, sensorNameToDbusName(sensorName),
810 missingAcceptableSensorInterfaces);
811 }
James Feist50fdfe32018-09-24 15:51:09 -0700812
Jason Lingf3b04fd2020-07-24 09:33:04 -0700813 for (const SensorInterfaceType& inputSensorInterface :
814 inputSensorInterfaces)
James Feist1738e2a2019-02-04 15:57:03 -0800815 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700816 const std::string& dbusInterface =
817 inputSensorInterface.second;
818 const std::string& inputSensorPath =
819 inputSensorInterface.first;
Josh Lehanfb82a872020-09-20 21:48:22 -0700820
821 // Setting timeout to 0 is intentional, as D-Bus passive
822 // sensor updates are pushed in, not pulled by timer poll.
823 // Setting ignoreDbusMinMax is intentional, as this
824 // prevents normalization of values to [0.0, 1.0] range,
825 // which would mess up the PID loop math.
826 // All non-fan PID classes should be initialized this way.
827 // As for why a fan should not use this code path, see
828 // the ed1dafdf168def37c65bfb7a5efd18d9dbe04727 commit.
Josh Lehan23e22b92022-11-12 22:37:58 -0800829 if ((pidClass == "temp") || (pidClass == "margin") ||
830 (pidClass == "power") || (pidClass == "powersum"))
James Feist50fdfe32018-09-24 15:51:09 -0700831 {
Harvey.Wued1dafd2022-02-09 13:53:20 +0800832 std::string inputSensorName =
833 getSensorNameFromPath(inputSensorPath);
834 auto& config = sensorConfig[inputSensorName];
Chaul Lya552fe22024-11-15 10:20:28 +0000835 archivedInputSensorNames.push_back(inputSensorName);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800836 config.type = pidClass;
837 config.readPath = inputSensorInterface.first;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700838 config.timeout = 0;
839 config.ignoreDbusMinMax = true;
Alex.Song8f73ad72021-10-07 00:18:27 +0800840 config.unavailableAsFailed = unavailableAsFailed;
James Feist50fdfe32018-09-24 15:51:09 -0700841 }
Josh Lehanfb82a872020-09-20 21:48:22 -0700842
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100843 if (dbusInterface != SensorValue::interface)
James Feist50fdfe32018-09-24 15:51:09 -0700844 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700845 /* all expected inputs in the configuration are expected
846 * to be sensor interfaces
847 */
Ed Tanous7d6e2252025-06-27 10:45:25 -0700848 throw std::runtime_error(std::format(
849 "sensor at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100850 inputSensorPath, dbusInterface,
851 SensorValue::interface));
James Feist50fdfe32018-09-24 15:51:09 -0700852 }
853 }
James Feist1738e2a2019-02-04 15:57:03 -0800854
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800855 // MissingIsAcceptable same postprocessing as Inputs
856 missingAcceptableSensorNames.clear();
857 for (const SensorInterfaceType&
858 missingAcceptableSensorInterface :
859 missingAcceptableSensorInterfaces)
860 {
861 const std::string& dbusInterface =
862 missingAcceptableSensorInterface.second;
863 const std::string& missingAcceptableSensorPath =
864 missingAcceptableSensorInterface.first;
865
866 std::string missingAcceptableSensorName =
867 getSensorNameFromPath(missingAcceptableSensorPath);
868 missingAcceptableSensorNames.push_back(
869 missingAcceptableSensorName);
870
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100871 if (dbusInterface != SensorValue::interface)
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800872 {
873 /* MissingIsAcceptable same error checking as Inputs
874 */
Ed Tanous7d6e2252025-06-27 10:45:25 -0700875 throw std::runtime_error(std::format(
876 "sensor at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
877 missingAcceptableSensorPath, dbusInterface,
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100878 SensorValue::interface));
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800879 }
880 }
881
Jason Lingf3b04fd2020-07-24 09:33:04 -0700882 /* fan pids need to pair up tach sensors with their pwm
883 * counterparts
884 */
885 if (pidClass == "fan")
886 {
887 /* If a PID is a fan there should be either
888 * (1) one output(pwm) per input(tach)
889 * OR
890 * (2) one putput(pwm) for all inputs(tach)
891 * everything else indicates a bad configuration.
892 */
893 bool singlePwm = false;
894 if (outputSensorInterfaces.size() == 1)
895 {
896 /* one pwm, set write paths for all fan sensors to it */
897 singlePwm = true;
898 }
899 else if (inputSensorInterfaces.size() ==
900 outputSensorInterfaces.size())
901 {
902 /* one to one mapping, each fan sensor gets its own pwm
903 * control */
904 singlePwm = false;
905 }
906 else
907 {
908 throw std::runtime_error(
909 "fan PID has invalid number of Outputs");
910 }
911 std::string fanSensorName;
912 std::string pwmPath;
913 std::string pwmInterface;
Harvey.Wued1dafd2022-02-09 13:53:20 +0800914 std::string pwmSensorName;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700915 if (singlePwm)
916 {
917 /* if just a single output(pwm) is provided then use
918 * that pwm control path for all the fan sensor write
919 * path configs
920 */
921 pwmPath = outputSensorInterfaces.at(0).first;
922 pwmInterface = outputSensorInterfaces.at(0).second;
923 }
924 for (uint32_t idx = 0; idx < inputSensorInterfaces.size();
925 idx++)
926 {
927 if (!singlePwm)
928 {
929 pwmPath = outputSensorInterfaces.at(idx).first;
930 pwmInterface =
931 outputSensorInterfaces.at(idx).second;
932 }
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100933 if (ControlFanPwm::interface != pwmInterface)
Jason Lingf3b04fd2020-07-24 09:33:04 -0700934 {
Ed Tanous7d6e2252025-06-27 10:45:25 -0700935 throw std::runtime_error(std::format(
936 "fan pwm control at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100937 pwmPath, pwmInterface,
938 ControlFanPwm::interface));
Jason Lingf3b04fd2020-07-24 09:33:04 -0700939 }
940 const std::string& fanPath =
941 inputSensorInterfaces.at(idx).first;
942 fanSensorName = getSensorNameFromPath(fanPath);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800943 pwmSensorName = getSensorNameFromPath(pwmPath);
944 std::string fanPwmIndex = fanSensorName + pwmSensorName;
Chaul Lya552fe22024-11-15 10:20:28 +0000945 archivedInputSensorNames.push_back(fanPwmIndex);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800946 auto& fanConfig = sensorConfig[fanPwmIndex];
947 fanConfig.type = pidClass;
948 fanConfig.readPath = fanPath;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700949 fanConfig.writePath = pwmPath;
950 // todo: un-hardcode this if there are fans with
951 // different ranges
952 fanConfig.max = 255;
953 fanConfig.min = 0;
954 }
955 }
James Feist11d243d2019-06-24 16:18:40 -0700956 // if the sensors aren't available in the current state, don't
957 // add them to the configuration.
Chaul Lya552fe22024-11-15 10:20:28 +0000958 if (archivedInputSensorNames.empty())
James Feist11d243d2019-06-24 16:18:40 -0700959 {
960 continue;
961 }
962
James Feist5ec20272019-07-10 11:59:57 -0700963 std::string offsetType;
James Feist50fdfe32018-09-24 15:51:09 -0700964
James Feist5ec20272019-07-10 11:59:57 -0700965 // SetPointOffset is a threshold value to pull from the sensor
966 // to apply an offset. For upper thresholds this means the
967 // setpoint is usually negative.
968 auto findSetpointOffset = base.find("SetPointOffset");
969 if (findSetpointOffset != base.end())
James Feist22c257a2018-08-31 14:07:12 -0700970 {
James Feist5ec20272019-07-10 11:59:57 -0700971 offsetType =
972 std::get<std::string>(findSetpointOffset->second);
973 if (std::find(thresholds::types.begin(),
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400974 thresholds::types.end(), offsetType) ==
975 thresholds::types.end())
James Feist5ec20272019-07-10 11:59:57 -0700976 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400977 throw std::runtime_error(
978 "Unsupported type: " + offsetType);
James Feist5ec20272019-07-10 11:59:57 -0700979 }
980 }
981
Josh Lehan31058fd2023-01-13 11:06:16 -0800982 std::vector<double> inputTempToMargin;
983
984 auto findTempToMargin = base.find("TempToMargin");
985 if (findTempToMargin != base.end())
986 {
987 inputTempToMargin =
988 std::get<std::vector<double>>(findTempToMargin->second);
989 }
990
991 std::vector<pid_control::conf::SensorInput> sensorInputs =
Chaul Lya552fe22024-11-15 10:20:28 +0000992 spliceInputs(archivedInputSensorNames, inputTempToMargin,
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800993 missingAcceptableSensorNames);
Josh Lehan31058fd2023-01-13 11:06:16 -0800994
James Feist5ec20272019-07-10 11:59:57 -0700995 if (offsetType.empty())
996 {
ykchiu7c6d35d2023-05-10 17:01:46 +0800997 conf::ControllerInfo& info = conf[pidName];
Josh Lehan31058fd2023-01-13 11:06:16 -0800998 info.inputs = std::move(sensorInputs);
Patrick Venture73823182020-10-08 15:12:51 -0700999 populatePidInfo(bus, base, info, nullptr, sensorConfig);
James Feist22c257a2018-08-31 14:07:12 -07001000 }
1001 else
1002 {
James Feist5ec20272019-07-10 11:59:57 -07001003 // we have to split up the inputs, as in practice t-control
1004 // values will differ, making setpoints differ
Josh Lehan31058fd2023-01-13 11:06:16 -08001005 for (const pid_control::conf::SensorInput& input :
1006 sensorInputs)
James Feist5ec20272019-07-10 11:59:57 -07001007 {
Josh Lehan31058fd2023-01-13 11:06:16 -08001008 conf::ControllerInfo& info = conf[input.name];
James Feist5ec20272019-07-10 11:59:57 -07001009 info.inputs.emplace_back(input);
Patrick Venture73823182020-10-08 15:12:51 -07001010 populatePidInfo(bus, base, info, &offsetType,
1011 sensorConfig);
James Feist5ec20272019-07-10 11:59:57 -07001012 }
James Feist22c257a2018-08-31 14:07:12 -07001013 }
James Feist22c257a2018-08-31 14:07:12 -07001014 }
1015 }
1016 auto findStepwise =
1017 configuration.second.find(stepwiseConfigurationInterface);
1018 if (findStepwise != configuration.second.end())
1019 {
1020 const auto& base = findStepwise->second;
ykchiu7c6d35d2023-05-10 17:01:46 +08001021 const std::string pidName =
1022 sensorNameToDbusName(std::get<std::string>(base.at("Name")));
James Feist22c257a2018-08-31 14:07:12 -07001023 const std::vector<std::string>& zones =
James Feist1f802f52019-02-08 13:51:43 -08001024 std::get<std::vector<std::string>>(base.at("Zones"));
James Feist22c257a2018-08-31 14:07:12 -07001025 for (const std::string& zone : zones)
1026 {
Josh Lehan998fbe62020-09-20 21:21:05 -07001027 auto index = getZoneIndex(zone, foundZones);
1028
James Feistf81f2882019-02-26 11:26:36 -08001029 conf::PIDConf& conf = zoneConfig[index];
James Feist50fdfe32018-09-24 15:51:09 -07001030
1031 std::vector<std::string> inputs;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001032 std::vector<std::string> missingAcceptableSensors;
1033 std::vector<std::string> missingAcceptableSensorNames;
James Feist50fdfe32018-09-24 15:51:09 -07001034 std::vector<std::string> sensorNames =
James Feist1f802f52019-02-08 13:51:43 -08001035 std::get<std::vector<std::string>>(base.at("Inputs"));
James Feist50fdfe32018-09-24 15:51:09 -07001036
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001037 auto findMissingAcceptable = base.find("MissingIsAcceptable");
1038 if (findMissingAcceptable != base.end())
1039 {
1040 missingAcceptableSensorNames =
1041 std::get<std::vector<std::string>>(
1042 findMissingAcceptable->second);
1043 }
1044
Alex.Song8f73ad72021-10-07 00:18:27 +08001045 bool unavailableAsFailed = true;
1046 auto findUnavailableAsFailed =
1047 base.find("InputUnavailableAsFailed");
1048 if (findUnavailableAsFailed != base.end())
1049 {
1050 unavailableAsFailed =
1051 std::get<bool>(findUnavailableAsFailed->second);
1052 }
1053
James Feist1738e2a2019-02-04 15:57:03 -08001054 bool sensorFound = false;
James Feist50fdfe32018-09-24 15:51:09 -07001055 for (const std::string& sensorName : sensorNames)
1056 {
James Feist1738e2a2019-02-04 15:57:03 -08001057 std::vector<std::pair<std::string, std::string>>
1058 sensorPathIfacePairs;
Jason Lingf3b04fd2020-07-24 09:33:04 -07001059 if (!findSensors(sensors, sensorNameToDbusName(sensorName),
1060 sensorPathIfacePairs))
James Feist50fdfe32018-09-24 15:51:09 -07001061 {
Chaul Lya552fe22024-11-15 10:20:28 +00001062#ifndef HANDLE_MISSING_OBJECT_PATHS
James Feist50fdfe32018-09-24 15:51:09 -07001063 break;
Chaul Lya552fe22024-11-15 10:20:28 +00001064#else
1065 if (std::find(missingAcceptableSensorNames.begin(),
1066 missingAcceptableSensorNames.end(),
1067 sensorName) ==
1068 missingAcceptableSensorNames.end())
1069 {
1070 // When an input sensor is NOT on DBus, and it's NOT
1071 // in the MissingIsAcceptable list. Build it as a
1072 // failed sensor with default information (temp
1073 // sensor path, temp type, ...)
1074 std::cerr
1075 << "Stepwise controller: Missing a missing-unacceptable sensor from D-Bus "
1076 << sensorName << "\n";
1077 std::string shortName =
1078 sensorNameToDbusName(sensorName);
1079
1080 inputs.push_back(shortName);
1081 auto& config = sensorConfig[shortName];
1082 config.type = "temp";
1083 config.readPath =
1084 getSensorPath(config.type, shortName);
1085 config.ignoreDbusMinMax = true;
1086 config.unavailableAsFailed = unavailableAsFailed;
1087 // todo: maybe un-hardcode this if we run into
1088 // slower timeouts with sensors
1089
1090 config.timeout = 0;
1091 sensorFound = true;
1092 }
1093 else
1094 {
1095 // When an input sensor is NOT on DBus, and it's in
1096 // the MissingIsAcceptable list. Ignore it and
1097 // continue with the next input sensor.
1098 std::cout
1099 << "Stepwise controller: Missing a missing-acceptable sensor from D-Bus "
1100 << sensorName << "\n";
1101 continue;
1102 }
1103#endif
James Feist50fdfe32018-09-24 15:51:09 -07001104 }
Chaul Lya552fe22024-11-15 10:20:28 +00001105 else
James Feist1738e2a2019-02-04 15:57:03 -08001106 {
Chaul Lya552fe22024-11-15 10:20:28 +00001107 for (const auto& sensorPathIfacePair :
1108 sensorPathIfacePairs)
1109 {
1110 std::string shortName = getSensorNameFromPath(
1111 sensorPathIfacePair.first);
James Feist50fdfe32018-09-24 15:51:09 -07001112
Chaul Lya552fe22024-11-15 10:20:28 +00001113 inputs.push_back(shortName);
1114 auto& config = sensorConfig[shortName];
1115 config.readPath = sensorPathIfacePair.first;
1116 config.type = "temp";
1117 config.ignoreDbusMinMax = true;
1118 config.unavailableAsFailed = unavailableAsFailed;
1119 // todo: maybe un-hardcode this if we run into
1120 // slower timeouts with sensors
James Feist1738e2a2019-02-04 15:57:03 -08001121
Chaul Lya552fe22024-11-15 10:20:28 +00001122 config.timeout = 0;
1123 sensorFound = true;
1124 }
James Feist1738e2a2019-02-04 15:57:03 -08001125 }
James Feist50fdfe32018-09-24 15:51:09 -07001126 }
1127 if (!sensorFound)
1128 {
1129 continue;
1130 }
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001131
1132 // MissingIsAcceptable same postprocessing as Inputs
1133 for (const std::string& missingAcceptableSensorName :
1134 missingAcceptableSensorNames)
1135 {
1136 std::vector<std::pair<std::string, std::string>>
1137 sensorPathIfacePairs;
1138 if (!findSensors(
1139 sensors,
1140 sensorNameToDbusName(missingAcceptableSensorName),
1141 sensorPathIfacePairs))
1142 {
Chaul Lya552fe22024-11-15 10:20:28 +00001143#ifndef HANDLE_MISSING_OBJECT_PATHS
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001144 break;
Chaul Lya552fe22024-11-15 10:20:28 +00001145#else
1146 // When a sensor in the MissingIsAcceptable list is NOT
1147 // on DBus and it still reaches here, which contradicts
1148 // to what we did in the Input sensor building step.
1149 // Continue.
1150 continue;
1151#endif
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001152 }
1153
1154 for (const auto& sensorPathIfacePair : sensorPathIfacePairs)
1155 {
1156 std::string shortName =
1157 getSensorNameFromPath(sensorPathIfacePair.first);
1158
1159 missingAcceptableSensors.push_back(shortName);
1160 }
1161 }
1162
ykchiu7c6d35d2023-05-10 17:01:46 +08001163 conf::ControllerInfo& info = conf[pidName];
Josh Lehan31058fd2023-01-13 11:06:16 -08001164
1165 std::vector<double> inputTempToMargin;
1166
1167 auto findTempToMargin = base.find("TempToMargin");
1168 if (findTempToMargin != base.end())
1169 {
1170 inputTempToMargin =
1171 std::get<std::vector<double>>(findTempToMargin->second);
1172 }
1173
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001174 info.inputs = spliceInputs(inputs, inputTempToMargin,
1175 missingAcceptableSensors);
James Feist50fdfe32018-09-24 15:51:09 -07001176
James Feist22c257a2018-08-31 14:07:12 -07001177 info.type = "stepwise";
1178 info.stepwiseInfo.ts = 1.0; // currently unused
James Feist3dfaafd2018-09-20 15:46:58 -07001179 info.stepwiseInfo.positiveHysteresis = 0.0;
1180 info.stepwiseInfo.negativeHysteresis = 0.0;
James Feist608304d2019-02-25 10:01:42 -08001181 std::string subtype = std::get<std::string>(base.at("Class"));
1182
1183 info.stepwiseInfo.isCeiling = (subtype == "Ceiling");
James Feist3dfaafd2018-09-20 15:46:58 -07001184 auto findPosHyst = base.find("PositiveHysteresis");
1185 auto findNegHyst = base.find("NegativeHysteresis");
1186 if (findPosHyst != base.end())
1187 {
James Feist1f802f52019-02-08 13:51:43 -08001188 info.stepwiseInfo.positiveHysteresis = std::visit(
James Feist208abce2018-12-06 09:59:10 -08001189 VariantToDoubleVisitor(), findPosHyst->second);
James Feist3dfaafd2018-09-20 15:46:58 -07001190 }
1191 if (findNegHyst != base.end())
1192 {
James Feist5782ab82019-04-02 08:38:48 -07001193 info.stepwiseInfo.negativeHysteresis = std::visit(
James Feist208abce2018-12-06 09:59:10 -08001194 VariantToDoubleVisitor(), findNegHyst->second);
James Feist3dfaafd2018-09-20 15:46:58 -07001195 }
James Feist22c257a2018-08-31 14:07:12 -07001196 std::vector<double> readings =
James Feist1f802f52019-02-08 13:51:43 -08001197 std::get<std::vector<double>>(base.at("Reading"));
James Feist22c257a2018-08-31 14:07:12 -07001198 if (readings.size() > ec::maxStepwisePoints)
1199 {
1200 throw std::invalid_argument("Too many stepwise points.");
1201 }
1202 if (readings.empty())
1203 {
1204 throw std::invalid_argument(
1205 "Must have one stepwise point.");
1206 }
1207 std::copy(readings.begin(), readings.end(),
1208 info.stepwiseInfo.reading);
1209 if (readings.size() < ec::maxStepwisePoints)
1210 {
1211 info.stepwiseInfo.reading[readings.size()] =
Patrick Venture5f59c0f2018-11-11 12:55:14 -08001212 std::numeric_limits<double>::quiet_NaN();
James Feist22c257a2018-08-31 14:07:12 -07001213 }
1214 std::vector<double> outputs =
James Feist1f802f52019-02-08 13:51:43 -08001215 std::get<std::vector<double>>(base.at("Output"));
James Feist22c257a2018-08-31 14:07:12 -07001216 if (readings.size() != outputs.size())
1217 {
1218 throw std::invalid_argument(
1219 "Outputs size must match readings");
1220 }
1221 std::copy(outputs.begin(), outputs.end(),
1222 info.stepwiseInfo.output);
1223 if (outputs.size() < ec::maxStepwisePoints)
1224 {
1225 info.stepwiseInfo.output[outputs.size()] =
Patrick Venture5f59c0f2018-11-11 12:55:14 -08001226 std::numeric_limits<double>::quiet_NaN();
James Feist22c257a2018-08-31 14:07:12 -07001227 }
James Feist7136a5a2018-07-19 09:52:05 -07001228 }
1229 }
1230 }
Patrick Venture39199b42020-10-08 14:40:29 -07001231 if constexpr (pid_control::conf::DEBUG)
James Feist7136a5a2018-07-19 09:52:05 -07001232 {
Patrick Venture39199b42020-10-08 14:40:29 -07001233 debugPrint(sensorConfig, zoneConfig, zoneDetailsConfig);
James Feist7136a5a2018-07-19 09:52:05 -07001234 }
James Feistc959c422018-11-01 12:33:40 -07001235 if (zoneConfig.empty() || zoneDetailsConfig.empty())
James Feist50fdfe32018-09-24 15:51:09 -07001236 {
James Feist1fe08952019-05-07 09:17:16 -07001237 std::cerr
1238 << "No fan zones, application pausing until new configuration\n";
1239 return false;
James Feist50fdfe32018-09-24 15:51:09 -07001240 }
James Feist1fe08952019-05-07 09:17:16 -07001241 return true;
James Feist7136a5a2018-07-19 09:52:05 -07001242}
Patrick Venturea0764872020-08-08 07:48:43 -07001243
James Feist7136a5a2018-07-19 09:52:05 -07001244} // namespace dbus_configuration
Patrick Venturea0764872020-08-08 07:48:43 -07001245} // namespace pid_control