blob: a624ceec5ff6d0b58b64d12f8d60f35736ccbb34 [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 Hansen1ec3d132025-10-27 17:19:50 +010036#include <xyz/openbmc_project/ObjectMapper/common.hpp>
Alexander Hansendae4a3a2025-11-11 17:12:01 +010037#include <xyz/openbmc_project/Sensor/Value/client.hpp>
Patrick Venturea83a3ec2020-08-04 09:52:05 -070038
39#include <algorithm>
Ed Tanousf8b6e552025-06-27 13:27:50 -070040#include <array>
James Feist64f072a2018-08-10 16:39:24 -070041#include <chrono>
Ed Tanousf8b6e552025-06-27 13:27:50 -070042#include <cstdint>
43#include <format>
James Feist7136a5a2018-07-19 09:52:05 -070044#include <iostream>
Ed Tanousf8b6e552025-06-27 13:27:50 -070045#include <limits>
James Feist1fe08952019-05-07 09:17:16 -070046#include <list>
Ed Tanousf8b6e552025-06-27 13:27:50 -070047#include <map>
48#include <stdexcept>
49#include <string>
50#include <tuple>
James Feist7136a5a2018-07-19 09:52:05 -070051#include <unordered_map>
Ed Tanousf8b6e552025-06-27 13:27:50 -070052#include <utility>
James Feist1f802f52019-02-08 13:51:43 -080053#include <variant>
Ed Tanousf8b6e552025-06-27 13:27:50 -070054#include <vector>
James Feist7136a5a2018-07-19 09:52:05 -070055
Alexander Hansen1ec3d132025-10-27 17:19:50 +010056using ObjectMapper = sdbusplus::common::xyz::openbmc_project::ObjectMapper;
Alexander Hansendae4a3a2025-11-11 17:12:01 +010057using SensorValue = sdbusplus::common::xyz::openbmc_project::sensor::Value;
Alexander Hansenbd2c98e2025-11-12 12:47:00 +010058using ControlFanPwm = sdbusplus::common::xyz::openbmc_project::control::FanPwm;
Alexander Hansen1ec3d132025-10-27 17:19:50 +010059
Patrick Venturea0764872020-08-08 07:48:43 -070060namespace pid_control
61{
62
Patrick Venturee2ec0f62018-09-04 12:30:27 -070063constexpr const char* pidConfigurationInterface =
James Feist7136a5a2018-07-19 09:52:05 -070064 "xyz.openbmc_project.Configuration.Pid";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070065constexpr const char* objectManagerInterface =
James Feist7136a5a2018-07-19 09:52:05 -070066 "org.freedesktop.DBus.ObjectManager";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070067constexpr const char* pidZoneConfigurationInterface =
James Feist7136a5a2018-07-19 09:52:05 -070068 "xyz.openbmc_project.Configuration.Pid.Zone";
James Feist22c257a2018-08-31 14:07:12 -070069constexpr const char* stepwiseConfigurationInterface =
70 "xyz.openbmc_project.Configuration.Stepwise";
James Feistf0096a02019-02-21 11:25:22 -080071constexpr const char* thermalControlIface =
72 "xyz.openbmc_project.Control.ThermalMode";
James Feist7136a5a2018-07-19 09:52:05 -070073
James Feist991ebd82020-07-21 11:14:52 -070074using Association = std::tuple<std::string, std::string, std::string>;
75using Associations = std::vector<Association>;
76
James Feist5ec20272019-07-10 11:59:57 -070077namespace thresholds
78{
79constexpr const char* warningInterface =
80 "xyz.openbmc_project.Sensor.Threshold.Warning";
81constexpr const char* criticalInterface =
82 "xyz.openbmc_project.Sensor.Threshold.Critical";
83const std::array<const char*, 4> types = {"CriticalLow", "CriticalHigh",
84 "WarningLow", "WarningHigh"};
85
86} // namespace thresholds
87
James Feist7136a5a2018-07-19 09:52:05 -070088namespace dbus_configuration
89{
Jason Lingf3b04fd2020-07-24 09:33:04 -070090using SensorInterfaceType = std::pair<std::string, std::string>;
91
92inline std::string getSensorNameFromPath(const std::string& dbusPath)
93{
Ed Tanousd2768c52025-06-26 11:42:57 -070094 return dbusPath.substr(dbusPath.find_last_of('/') + 1);
Jason Lingf3b04fd2020-07-24 09:33:04 -070095}
96
97inline std::string sensorNameToDbusName(const std::string& sensorName)
98{
99 std::string retString = sensorName;
100 std::replace(retString.begin(), retString.end(), ' ', '_');
101 return retString;
102}
James Feist5ec20272019-07-10 11:59:57 -0700103
Patrick Williamsb228bc32022-07-22 19:26:56 -0500104std::vector<std::string> getSelectedProfiles(sdbusplus::bus_t& bus)
James Feistf0096a02019-02-21 11:25:22 -0800105{
106 std::vector<std::string> ret;
Alexander Hansen1ec3d132025-10-27 17:19:50 +0100107 auto mapper = bus.new_method_call(
108 ObjectMapper::default_service, ObjectMapper::instance_path,
109 ObjectMapper::interface, ObjectMapper::method_names::get_sub_tree);
James Feistf0096a02019-02-21 11:25:22 -0800110 mapper.append("/", 0, std::array<const char*, 1>{thermalControlIface});
111 std::unordered_map<
112 std::string, std::unordered_map<std::string, std::vector<std::string>>>
113 respData;
114
115 try
116 {
117 auto resp = bus.call(mapper);
118 resp.read(respData);
119 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500120 catch (const sdbusplus::exception_t&)
James Feistf0096a02019-02-21 11:25:22 -0800121 {
122 // can't do anything without mapper call data
123 throw std::runtime_error("ObjectMapper Call Failure");
124 }
125 if (respData.empty())
126 {
127 // if the user has profiles but doesn't expose the interface to select
128 // one, just go ahead without using profiles
129 return ret;
130 }
131
132 // assumption is that we should only have a small handful of selected
133 // profiles at a time (probably only 1), so calling each individually should
134 // not incur a large cost
135 for (const auto& objectPair : respData)
136 {
137 const std::string& path = objectPair.first;
138 for (const auto& ownerPair : objectPair.second)
139 {
140 const std::string& busName = ownerPair.first;
141 auto getProfile =
142 bus.new_method_call(busName.c_str(), path.c_str(),
143 "org.freedesktop.DBus.Properties", "Get");
144 getProfile.append(thermalControlIface, "Current");
145 std::variant<std::string> variantResp;
146 try
147 {
148 auto resp = bus.call(getProfile);
149 resp.read(variantResp);
150 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500151 catch (const sdbusplus::exception_t&)
James Feistf0096a02019-02-21 11:25:22 -0800152 {
153 throw std::runtime_error("Failure getting profile");
154 }
155 std::string mode = std::get<std::string>(variantResp);
156 ret.emplace_back(std::move(mode));
157 }
158 }
Patrick Venture39199b42020-10-08 14:40:29 -0700159 if constexpr (pid_control::conf::DEBUG)
James Feistf0096a02019-02-21 11:25:22 -0800160 {
161 std::cout << "Profiles selected: ";
162 for (const auto& profile : ret)
163 {
164 std::cout << profile << " ";
165 }
166 std::cout << "\n";
167 }
168 return ret;
169}
170
James Feist991ebd82020-07-21 11:14:52 -0700171int eventHandler(sd_bus_message* m, void* context, sd_bus_error*)
James Feist7136a5a2018-07-19 09:52:05 -0700172{
James Feist991ebd82020-07-21 11:14:52 -0700173 if (context == nullptr || m == nullptr)
James Feist1fe08952019-05-07 09:17:16 -0700174 {
175 throw std::runtime_error("Invalid match");
176 }
James Feist991ebd82020-07-21 11:14:52 -0700177
178 // we skip associations because the mapper populates these, not the sensors
Josh Lehan10e46ef2023-02-01 18:25:58 -0800179 const std::array<const char*, 2> skipList = {
180 "xyz.openbmc_project.Association",
181 "xyz.openbmc_project.Association.Definitions"};
James Feist991ebd82020-07-21 11:14:52 -0700182
Patrick Williamsb228bc32022-07-22 19:26:56 -0500183 sdbusplus::message_t message(m);
James Feist991ebd82020-07-21 11:14:52 -0700184 if (std::string(message.get_member()) == "InterfacesAdded")
185 {
186 sdbusplus::message::object_path path;
187 std::unordered_map<
188 std::string,
189 std::unordered_map<std::string, std::variant<Associations, bool>>>
190 data;
191
192 message.read(path, data);
193
194 for (const char* skip : skipList)
195 {
196 auto find = data.find(skip);
197 if (find != data.end())
198 {
199 data.erase(find);
200 if (data.empty())
201 {
202 return 1;
203 }
204 }
205 }
Josh Lehan10e46ef2023-02-01 18:25:58 -0800206
207 if constexpr (pid_control::conf::DEBUG)
208 {
209 std::cout << "New config detected: " << path.str << std::endl;
210 for (auto& d : data)
211 {
212 std::cout << "\tdata is " << d.first << std::endl;
213 for (auto& second : d.second)
214 {
215 std::cout << "\t\tdata is " << second.first << std::endl;
216 }
217 }
218 }
James Feist991ebd82020-07-21 11:14:52 -0700219 }
220
James Feist1fe08952019-05-07 09:17:16 -0700221 boost::asio::steady_timer* timer =
222 static_cast<boost::asio::steady_timer*>(context);
223
224 // do a brief sleep as we tend to get a bunch of these events at
225 // once
226 timer->expires_after(std::chrono::seconds(2));
227 timer->async_wait([](const boost::system::error_code ec) {
228 if (ec == boost::asio::error::operation_aborted)
229 {
230 /* another timer started*/
231 return;
232 }
233
234 std::cout << "New configuration detected, reloading\n.";
Yong Li298a95c2020-04-07 15:11:02 +0800235 tryRestartControlLoops();
James Feist1fe08952019-05-07 09:17:16 -0700236 });
237
238 return 1;
239}
240
Patrick Williamsb228bc32022-07-22 19:26:56 -0500241void createMatches(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer)
James Feist1fe08952019-05-07 09:17:16 -0700242{
243 // this is a list because the matches can't be moved
Patrick Williamsb228bc32022-07-22 19:26:56 -0500244 static std::list<sdbusplus::bus::match_t> matches;
James Feist1fe08952019-05-07 09:17:16 -0700245
James Feist3987c8b2019-05-13 10:43:17 -0700246 const std::array<std::string, 4> interfaces = {
247 thermalControlIface, pidConfigurationInterface,
248 pidZoneConfigurationInterface, stepwiseConfigurationInterface};
James Feist1fe08952019-05-07 09:17:16 -0700249
250 // this list only needs to be created once
251 if (!matches.empty())
252 {
253 return;
254 }
255
256 // we restart when the configuration changes or there are new sensors
257 for (const auto& interface : interfaces)
258 {
259 matches.emplace_back(
260 bus,
261 "type='signal',member='PropertiesChanged',arg0namespace='" +
262 interface + "'",
263 eventHandler, &timer);
264 }
265 matches.emplace_back(
266 bus,
267 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
268 "sensors/'",
269 eventHandler, &timer);
Jinliang Wangc2a311b2023-04-26 18:36:56 +0000270 matches.emplace_back(bus,
271 "type='signal',member='InterfacesRemoved',arg0path='/"
272 "xyz/openbmc_project/sensors/'",
273 eventHandler, &timer);
James Feist1fe08952019-05-07 09:17:16 -0700274}
275
Jason Ling6fc301f2020-07-23 12:39:57 -0700276/**
277 * retrieve an attribute from the pid configuration map
278 * @param[in] base - the PID configuration map, keys are the attributes and
279 * value is the variant associated with that attribute.
280 * @param attributeName - the name of the attribute
281 * @return a variant holding the value associated with a key
282 * @throw runtime_error : attributeName is not in base
283 */
284inline DbusVariantType getPIDAttribute(
285 const std::unordered_map<std::string, DbusVariantType>& base,
286 const std::string& attributeName)
287{
288 auto search = base.find(attributeName);
289 if (search == base.end())
290 {
291 throw std::runtime_error("missing attribute " + attributeName);
292 }
293 return search->second;
294}
295
Harvey Wu239aa7d2022-11-18 08:43:34 +0800296inline void getCycleTimeSetting(
297 const std::unordered_map<std::string, DbusVariantType>& zone,
298 const int zoneIndex, const std::string& attributeName, uint64_t& value)
299{
300 auto findAttributeName = zone.find(attributeName);
301 if (findAttributeName != zone.end())
302 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400303 double tmpAttributeValue =
304 std::visit(VariantToDoubleVisitor(), zone.at(attributeName));
Harvey Wu239aa7d2022-11-18 08:43:34 +0800305 if (tmpAttributeValue >= 1.0)
306 {
307 value = static_cast<uint64_t>(tmpAttributeValue);
308 }
309 else
310 {
311 std::cerr << "Zone " << zoneIndex << ": " << attributeName
312 << " is invalid. Use default " << value << " ms\n";
313 }
314 }
315 else
316 {
317 std::cerr << "Zone " << zoneIndex << ": " << attributeName
318 << " cannot find setting. Use default " << value << " ms\n";
319 }
320}
321
James Feist5ec20272019-07-10 11:59:57 -0700322void populatePidInfo(
Patrick Williamscd1e78a2025-04-07 17:21:05 -0400323 sdbusplus::bus_t& bus,
James Feist5ec20272019-07-10 11:59:57 -0700324 const std::unordered_map<std::string, DbusVariantType>& base,
Patrick Venture1df9e872020-10-08 15:35:01 -0700325 conf::ControllerInfo& info, const std::string* thresholdProperty,
Patrick Venture73823182020-10-08 15:12:51 -0700326 const std::map<std::string, conf::SensorConfig>& sensorConfig)
James Feist5ec20272019-07-10 11:59:57 -0700327{
Jason Ling6fc301f2020-07-23 12:39:57 -0700328 info.type = std::get<std::string>(getPIDAttribute(base, "Class"));
James Feist5ec20272019-07-10 11:59:57 -0700329 if (info.type == "fan")
330 {
331 info.setpoint = 0;
332 }
333 else
334 {
Jason Ling6fc301f2020-07-23 12:39:57 -0700335 info.setpoint = std::visit(VariantToDoubleVisitor(),
336 getPIDAttribute(base, "SetPoint"));
James Feist5ec20272019-07-10 11:59:57 -0700337 }
338
ykchiu9fe3a3c2023-05-11 13:43:54 +0800339 int failsafepercent = 0;
340 auto findFailSafe = base.find("FailSafePercent");
341 if (findFailSafe != base.end())
342 {
343 failsafepercent = std::visit(VariantToDoubleVisitor(),
344 getPIDAttribute(base, "FailSafePercent"));
345 }
346 info.failSafePercent = failsafepercent;
347
James Feist5ec20272019-07-10 11:59:57 -0700348 if (thresholdProperty != nullptr)
349 {
350 std::string interface;
351 if (*thresholdProperty == "WarningHigh" ||
352 *thresholdProperty == "WarningLow")
353 {
354 interface = thresholds::warningInterface;
355 }
356 else
357 {
358 interface = thresholds::criticalInterface;
359 }
Josh Lehan31058fd2023-01-13 11:06:16 -0800360
361 // Although this checks only the first vector element for the
362 // named threshold, it is OK, because the SetPointOffset parser
363 // splits up the input into individual vectors, each with only a
364 // single element, if it detects that SetPointOffset is in use.
365 const std::string& path =
366 sensorConfig.at(info.inputs.front().name).readPath;
James Feist5ec20272019-07-10 11:59:57 -0700367
Patrick Williamscd1e78a2025-04-07 17:21:05 -0400368 DbusHelper helper(bus);
Patrick Venture9b936922020-08-10 11:28:39 -0700369 std::string service = helper.getService(interface, path);
James Feist5ec20272019-07-10 11:59:57 -0700370 double reading = 0;
371 try
372 {
Patrick Venture9b936922020-08-10 11:28:39 -0700373 helper.getProperty(service, path, interface, *thresholdProperty,
374 reading);
James Feist5ec20272019-07-10 11:59:57 -0700375 }
Patrick Williamsb228bc32022-07-22 19:26:56 -0500376 catch (const sdbusplus::exception_t& ex)
James Feist5ec20272019-07-10 11:59:57 -0700377 {
378 // unsupported threshold, leaving reading at 0
379 }
380
381 info.setpoint += reading;
382 }
383
384 info.pidInfo.ts = 1.0; // currently unused
Jason Ling6fc301f2020-07-23 12:39:57 -0700385 info.pidInfo.proportionalCoeff = std::visit(
386 VariantToDoubleVisitor(), getPIDAttribute(base, "PCoefficient"));
387 info.pidInfo.integralCoeff = std::visit(
388 VariantToDoubleVisitor(), getPIDAttribute(base, "ICoefficient"));
Josh Lehanc612c052022-12-12 09:56:47 -0800389 // DCoefficient is below, it is optional, same reason as in buildjson.cpp
Jason Ling6fc301f2020-07-23 12:39:57 -0700390 info.pidInfo.feedFwdOffset = std::visit(
391 VariantToDoubleVisitor(), getPIDAttribute(base, "FFOffCoefficient"));
392 info.pidInfo.feedFwdGain = std::visit(
393 VariantToDoubleVisitor(), getPIDAttribute(base, "FFGainCoefficient"));
394 info.pidInfo.integralLimit.max = std::visit(
395 VariantToDoubleVisitor(), getPIDAttribute(base, "ILimitMax"));
396 info.pidInfo.integralLimit.min = std::visit(
397 VariantToDoubleVisitor(), getPIDAttribute(base, "ILimitMin"));
398 info.pidInfo.outLim.max = std::visit(VariantToDoubleVisitor(),
399 getPIDAttribute(base, "OutLimitMax"));
400 info.pidInfo.outLim.min = std::visit(VariantToDoubleVisitor(),
401 getPIDAttribute(base, "OutLimitMin"));
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400402 info.pidInfo.slewNeg =
403 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewNeg"));
404 info.pidInfo.slewPos =
405 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewPos"));
Josh Lehanc612c052022-12-12 09:56:47 -0800406
Delphine CC Chiu97889632023-11-06 11:32:46 +0800407 bool checkHysterWithSetpt = false;
James Feist5ec20272019-07-10 11:59:57 -0700408 double negativeHysteresis = 0;
409 double positiveHysteresis = 0;
Josh Lehanc612c052022-12-12 09:56:47 -0800410 double derivativeCoeff = 0;
James Feist5ec20272019-07-10 11:59:57 -0700411
Delphine CC Chiu5d897e22024-06-04 13:33:22 +0800412 auto findCheckHysterFlag = base.find("CheckHysteresisWithSetpoint");
James Feist5ec20272019-07-10 11:59:57 -0700413 auto findNeg = base.find("NegativeHysteresis");
414 auto findPos = base.find("PositiveHysteresis");
Josh Lehanc612c052022-12-12 09:56:47 -0800415 auto findDerivative = base.find("DCoefficient");
James Feist5ec20272019-07-10 11:59:57 -0700416
Delphine CC Chiu97889632023-11-06 11:32:46 +0800417 if (findCheckHysterFlag != base.end())
418 {
419 checkHysterWithSetpt = std::get<bool>(findCheckHysterFlag->second);
420 }
James Feist5ec20272019-07-10 11:59:57 -0700421 if (findNeg != base.end())
422 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400423 negativeHysteresis =
424 std::visit(VariantToDoubleVisitor(), findNeg->second);
James Feist5ec20272019-07-10 11:59:57 -0700425 }
James Feist5ec20272019-07-10 11:59:57 -0700426 if (findPos != base.end())
427 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400428 positiveHysteresis =
429 std::visit(VariantToDoubleVisitor(), findPos->second);
James Feist5ec20272019-07-10 11:59:57 -0700430 }
Josh Lehanc612c052022-12-12 09:56:47 -0800431 if (findDerivative != base.end())
432 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400433 derivativeCoeff =
434 std::visit(VariantToDoubleVisitor(), findDerivative->second);
Josh Lehanc612c052022-12-12 09:56:47 -0800435 }
436
Delphine CC Chiu97889632023-11-06 11:32:46 +0800437 info.pidInfo.checkHysterWithSetpt = checkHysterWithSetpt;
James Feist5ec20272019-07-10 11:59:57 -0700438 info.pidInfo.negativeHysteresis = negativeHysteresis;
439 info.pidInfo.positiveHysteresis = positiveHysteresis;
Josh Lehanc612c052022-12-12 09:56:47 -0800440 info.pidInfo.derivativeCoeff = derivativeCoeff;
James Feist5ec20272019-07-10 11:59:57 -0700441}
442
Patrick Williamsb228bc32022-07-22 19:26:56 -0500443bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
Patrick Venture73823182020-10-08 15:12:51 -0700444 std::map<std::string, conf::SensorConfig>& sensorConfig,
445 std::map<int64_t, conf::PIDConf>& zoneConfig,
446 std::map<int64_t, conf::ZoneConfig>& zoneDetailsConfig)
James Feist1fe08952019-05-07 09:17:16 -0700447{
James Feist1fe08952019-05-07 09:17:16 -0700448 sensorConfig.clear();
449 zoneConfig.clear();
450 zoneDetailsConfig.clear();
451
452 createMatches(bus, timer);
453
Alexander Hansen1ec3d132025-10-27 17:19:50 +0100454 auto mapper = bus.new_method_call(
455 ObjectMapper::default_service, ObjectMapper::instance_path,
456 ObjectMapper::interface, ObjectMapper::method_names::get_sub_tree);
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400457 mapper.append(
458 "/", 0,
459 std::array<const char*, 6>{
460 objectManagerInterface, pidConfigurationInterface,
461 pidZoneConfigurationInterface, stepwiseConfigurationInterface,
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100462 SensorValue::interface, ControlFanPwm::interface});
James Feist7136a5a2018-07-19 09:52:05 -0700463 std::unordered_map<
464 std::string, std::unordered_map<std::string, std::vector<std::string>>>
465 respData;
James Feist22c257a2018-08-31 14:07:12 -0700466 try
467 {
468 auto resp = bus.call(mapper);
James Feist22c257a2018-08-31 14:07:12 -0700469 resp.read(respData);
470 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500471 catch (const sdbusplus::exception_t&)
James Feist22c257a2018-08-31 14:07:12 -0700472 {
473 // can't do anything without mapper call data
474 throw std::runtime_error("ObjectMapper Call Failure");
475 }
James Feist7136a5a2018-07-19 09:52:05 -0700476
James Feist7136a5a2018-07-19 09:52:05 -0700477 if (respData.empty())
478 {
James Feist22c257a2018-08-31 14:07:12 -0700479 // can't do anything without mapper call data
James Feist7136a5a2018-07-19 09:52:05 -0700480 throw std::runtime_error("No configuration data available from Mapper");
481 }
482 // create a map of pair of <has pid configuration, ObjectManager path>
483 std::unordered_map<std::string, std::pair<bool, std::string>> owners;
484 // and a map of <path, interface> for sensors
485 std::unordered_map<std::string, std::string> sensors;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700486 for (const auto& objectPair : respData)
James Feist7136a5a2018-07-19 09:52:05 -0700487 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700488 for (const auto& ownerPair : objectPair.second)
James Feist7136a5a2018-07-19 09:52:05 -0700489 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700490 auto& owner = owners[ownerPair.first];
491 for (const std::string& interface : ownerPair.second)
James Feist7136a5a2018-07-19 09:52:05 -0700492 {
James Feist7136a5a2018-07-19 09:52:05 -0700493 if (interface == objectManagerInterface)
494 {
495 owner.second = objectPair.first;
496 }
497 if (interface == pidConfigurationInterface ||
James Feist22c257a2018-08-31 14:07:12 -0700498 interface == pidZoneConfigurationInterface ||
499 interface == stepwiseConfigurationInterface)
James Feist7136a5a2018-07-19 09:52:05 -0700500 {
501 owner.first = true;
502 }
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100503 if (interface == SensorValue::interface ||
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100504 interface == ControlFanPwm::interface)
James Feist7136a5a2018-07-19 09:52:05 -0700505 {
506 // we're not interested in pwm sensors, just pwm control
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100507 if (interface == SensorValue::interface &&
James Feist7136a5a2018-07-19 09:52:05 -0700508 objectPair.first.find("pwm") != std::string::npos)
509 {
510 continue;
511 }
512 sensors[objectPair.first] = interface;
513 }
514 }
515 }
516 }
517 ManagedObjectType configurations;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700518 for (const auto& owner : owners)
James Feist7136a5a2018-07-19 09:52:05 -0700519 {
520 // skip if no pid configuration (means probably a sensor)
521 if (!owner.second.first)
522 {
523 continue;
524 }
525 auto endpoint = bus.new_method_call(
526 owner.first.c_str(), owner.second.second.c_str(),
527 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
James Feist22c257a2018-08-31 14:07:12 -0700528 ManagedObjectType configuration;
529 try
James Feist7136a5a2018-07-19 09:52:05 -0700530 {
Manojkiran Eda7ca88872024-06-17 11:55:48 +0530531 auto response = bus.call(endpoint);
532 response.read(configuration);
James Feist22c257a2018-08-31 14:07:12 -0700533 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500534 catch (const sdbusplus::exception_t&)
James Feist22c257a2018-08-31 14:07:12 -0700535 {
536 // this shouldn't happen, probably means daemon crashed
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400537 throw std::runtime_error(
538 "Error getting managed objects from " + owner.first);
James Feist7136a5a2018-07-19 09:52:05 -0700539 }
James Feist22c257a2018-08-31 14:07:12 -0700540
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700541 for (auto& pathPair : configuration)
James Feist7136a5a2018-07-19 09:52:05 -0700542 {
543 if (pathPair.second.find(pidConfigurationInterface) !=
544 pathPair.second.end() ||
545 pathPair.second.find(pidZoneConfigurationInterface) !=
James Feist22c257a2018-08-31 14:07:12 -0700546 pathPair.second.end() ||
547 pathPair.second.find(stepwiseConfigurationInterface) !=
James Feist7136a5a2018-07-19 09:52:05 -0700548 pathPair.second.end())
549 {
550 configurations.emplace(pathPair);
551 }
James Feistf0096a02019-02-21 11:25:22 -0800552 }
553 }
554
555 // remove controllers from config that aren't in the current profile(s)
James Feist3987c8b2019-05-13 10:43:17 -0700556 std::vector<std::string> selectedProfiles = getSelectedProfiles(bus);
557 if (selectedProfiles.size())
James Feistf0096a02019-02-21 11:25:22 -0800558 {
James Feist3987c8b2019-05-13 10:43:17 -0700559 for (auto pathIt = configurations.begin();
560 pathIt != configurations.end();)
James Feistf0096a02019-02-21 11:25:22 -0800561 {
James Feist3987c8b2019-05-13 10:43:17 -0700562 for (auto confIt = pathIt->second.begin();
563 confIt != pathIt->second.end();)
James Feistf0096a02019-02-21 11:25:22 -0800564 {
James Feist3987c8b2019-05-13 10:43:17 -0700565 auto profilesFind = confIt->second.find("Profiles");
566 if (profilesFind == confIt->second.end())
James Feistf0096a02019-02-21 11:25:22 -0800567 {
James Feist3987c8b2019-05-13 10:43:17 -0700568 confIt++;
569 continue; // if no profiles selected, apply always
570 }
571 auto profiles =
572 std::get<std::vector<std::string>>(profilesFind->second);
573 if (profiles.empty())
574 {
575 confIt++;
576 continue;
577 }
578
579 bool found = false;
580 for (const std::string& profile : profiles)
581 {
582 if (std::find(selectedProfiles.begin(),
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400583 selectedProfiles.end(), profile) !=
584 selectedProfiles.end())
James Feist3987c8b2019-05-13 10:43:17 -0700585 {
586 found = true;
587 break;
588 }
589 }
590 if (found)
591 {
592 confIt++;
James Feistf0096a02019-02-21 11:25:22 -0800593 }
594 else
595 {
James Feist3987c8b2019-05-13 10:43:17 -0700596 confIt = pathIt->second.erase(confIt);
James Feistf0096a02019-02-21 11:25:22 -0800597 }
598 }
James Feist3987c8b2019-05-13 10:43:17 -0700599 if (pathIt->second.empty())
James Feistf0096a02019-02-21 11:25:22 -0800600 {
James Feist3987c8b2019-05-13 10:43:17 -0700601 pathIt = configurations.erase(pathIt);
James Feistf0096a02019-02-21 11:25:22 -0800602 }
James Feist3987c8b2019-05-13 10:43:17 -0700603 else
James Feistf0096a02019-02-21 11:25:22 -0800604 {
James Feist3987c8b2019-05-13 10:43:17 -0700605 pathIt++;
James Feistf0096a02019-02-21 11:25:22 -0800606 }
James Feist7136a5a2018-07-19 09:52:05 -0700607 }
608 }
James Feist8c3c51e2018-08-08 16:31:43 -0700609
Josh Lehan998fbe62020-09-20 21:21:05 -0700610 // On D-Bus, although not necessary,
611 // having the "zoneID" field can still be useful,
612 // as it is used for diagnostic messages,
613 // logging file names, and so on.
614 // Accept optional "ZoneIndex" parameter to explicitly specify.
615 // If not present, or not unique, auto-assign index,
616 // using 0-based numbering, ensuring uniqueness.
617 std::map<std::string, int64_t> foundZones;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700618 for (const auto& configuration : configurations)
James Feist7136a5a2018-07-19 09:52:05 -0700619 {
620 auto findZone =
621 configuration.second.find(pidZoneConfigurationInterface);
622 if (findZone != configuration.second.end())
623 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700624 const auto& zone = findZone->second;
James Feistffd418b2018-11-15 14:46:36 -0800625
James Feist1f802f52019-02-08 13:51:43 -0800626 const std::string& name = std::get<std::string>(zone.at("Name"));
Josh Lehan998fbe62020-09-20 21:21:05 -0700627
628 auto findZoneIndex = zone.find("ZoneIndex");
629 if (findZoneIndex == zone.end())
630 {
631 continue;
632 }
633
634 auto ptrZoneIndex = std::get_if<double>(&(findZoneIndex->second));
635 if (!ptrZoneIndex)
636 {
637 continue;
638 }
639
640 auto desiredIndex = static_cast<int64_t>(*ptrZoneIndex);
641 auto grantedIndex = setZoneIndex(name, foundZones, desiredIndex);
642 std::cout << "Zone " << name << " is at ZoneIndex " << grantedIndex
643 << "\n";
644 }
645 }
646
647 for (const auto& configuration : configurations)
648 {
649 auto findZone =
650 configuration.second.find(pidZoneConfigurationInterface);
651 if (findZone != configuration.second.end())
652 {
653 const auto& zone = findZone->second;
654
655 const std::string& name = std::get<std::string>(zone.at("Name"));
656
657 auto index = getZoneIndex(name, foundZones);
James Feist8c3c51e2018-08-08 16:31:43 -0700658
Patrick Venturec54fbd82018-10-30 19:40:05 -0700659 auto& details = zoneDetailsConfig[index];
Josh Lehan998fbe62020-09-20 21:21:05 -0700660
James Feist3484bed2019-02-25 13:28:18 -0800661 details.minThermalOutput = std::visit(VariantToDoubleVisitor(),
662 zone.at("MinThermalOutput"));
ykchiu9fe3a3c2023-05-11 13:43:54 +0800663
664 int failsafepercent = 0;
665 auto findFailSafe = zone.find("FailSafePercent");
666 if (findFailSafe != zone.end())
667 {
668 failsafepercent = std::visit(VariantToDoubleVisitor(),
669 zone.at("FailSafePercent"));
670 }
671 details.failsafePercent = failsafepercent;
Josh Lehan9f9a06a2022-12-14 10:39:45 -0800672
Harvey Wu239aa7d2022-11-18 08:43:34 +0800673 getCycleTimeSetting(zone, index, "CycleIntervalTimeMS",
674 details.cycleTime.cycleIntervalTimeMS);
675 getCycleTimeSetting(zone, index, "UpdateThermalsTimeMS",
676 details.cycleTime.updateThermalsTimeMS);
Delphine CC Chiu97889632023-11-06 11:32:46 +0800677
678 bool accumulateSetPoint = false;
679 auto findAccSetPoint = zone.find("AccumulateSetPoint");
680 if (findAccSetPoint != zone.end())
681 {
682 accumulateSetPoint = std::get<bool>(findAccSetPoint->second);
683 }
684 details.accumulateSetPoint = accumulateSetPoint;
James Feist7136a5a2018-07-19 09:52:05 -0700685 }
686 auto findBase = configuration.second.find(pidConfigurationInterface);
Jason Lingf3b04fd2020-07-24 09:33:04 -0700687 // loop through all the PID configurations and fill out a sensor config
James Feist22c257a2018-08-31 14:07:12 -0700688 if (findBase != configuration.second.end())
James Feist7136a5a2018-07-19 09:52:05 -0700689 {
James Feist22c257a2018-08-31 14:07:12 -0700690 const auto& base =
691 configuration.second.at(pidConfigurationInterface);
ykchiu7c6d35d2023-05-10 17:01:46 +0800692 const std::string pidName =
693 sensorNameToDbusName(std::get<std::string>(base.at("Name")));
Jason Lingf3b04fd2020-07-24 09:33:04 -0700694 const std::string pidClass =
695 std::get<std::string>(base.at("Class"));
James Feist22c257a2018-08-31 14:07:12 -0700696 const std::vector<std::string>& zones =
James Feist1f802f52019-02-08 13:51:43 -0800697 std::get<std::vector<std::string>>(base.at("Zones"));
James Feist22c257a2018-08-31 14:07:12 -0700698 for (const std::string& zone : zones)
James Feist7136a5a2018-07-19 09:52:05 -0700699 {
Josh Lehan998fbe62020-09-20 21:21:05 -0700700 auto index = getZoneIndex(zone, foundZones);
701
James Feistf81f2882019-02-26 11:26:36 -0800702 conf::PIDConf& conf = zoneConfig[index];
Jason Lingf3b04fd2020-07-24 09:33:04 -0700703 std::vector<std::string> inputSensorNames(
704 std::get<std::vector<std::string>>(base.at("Inputs")));
705 std::vector<std::string> outputSensorNames;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800706 std::vector<std::string> missingAcceptableSensorNames;
Chaul Lya552fe22024-11-15 10:20:28 +0000707 std::vector<std::string> archivedInputSensorNames;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800708
709 auto findMissingAcceptable = base.find("MissingIsAcceptable");
710 if (findMissingAcceptable != base.end())
711 {
712 missingAcceptableSensorNames =
713 std::get<std::vector<std::string>>(
714 findMissingAcceptable->second);
715 }
James Feist50fdfe32018-09-24 15:51:09 -0700716
Jason Lingf3b04fd2020-07-24 09:33:04 -0700717 // assumption: all fan pids must have at least one output
718 if (pidClass == "fan")
James Feist50fdfe32018-09-24 15:51:09 -0700719 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700720 outputSensorNames = std::get<std::vector<std::string>>(
721 getPIDAttribute(base, "Outputs"));
James Feist50fdfe32018-09-24 15:51:09 -0700722 }
James Feist1738e2a2019-02-04 15:57:03 -0800723
Alex.Song8f73ad72021-10-07 00:18:27 +0800724 bool unavailableAsFailed = true;
725 auto findUnavailableAsFailed =
726 base.find("InputUnavailableAsFailed");
727 if (findUnavailableAsFailed != base.end())
728 {
729 unavailableAsFailed =
730 std::get<bool>(findUnavailableAsFailed->second);
731 }
732
Jason Lingf3b04fd2020-07-24 09:33:04 -0700733 std::vector<SensorInterfaceType> inputSensorInterfaces;
734 std::vector<SensorInterfaceType> outputSensorInterfaces;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800735 std::vector<SensorInterfaceType>
736 missingAcceptableSensorInterfaces;
737
Jason Lingf3b04fd2020-07-24 09:33:04 -0700738 /* populate an interface list for different sensor direction
739 * types (input,output)
740 */
741 /* take the Inputs from the configuration and generate
742 * a list of dbus descriptors (path, interface).
743 * Mapping can be many-to-one since an element of Inputs can be
744 * a regex
745 */
746 for (const std::string& sensorName : inputSensorNames)
James Feist50fdfe32018-09-24 15:51:09 -0700747 {
Chaul Lya552fe22024-11-15 10:20:28 +0000748#ifndef HANDLE_MISSING_OBJECT_PATHS
Jason Lingf3b04fd2020-07-24 09:33:04 -0700749 findSensors(sensors, sensorNameToDbusName(sensorName),
750 inputSensorInterfaces);
Chaul Lya552fe22024-11-15 10:20:28 +0000751#else
752 std::vector<std::pair<std::string, std::string>>
753 sensorPathIfacePairs;
754 auto found =
755 findSensors(sensors, sensorNameToDbusName(sensorName),
756 sensorPathIfacePairs);
757 if (found)
758 {
759 inputSensorInterfaces.insert(
760 inputSensorInterfaces.end(),
761 sensorPathIfacePairs.begin(),
762 sensorPathIfacePairs.end());
763 }
764 else if (pidClass != "fan")
765 {
766 if (std::find(missingAcceptableSensorNames.begin(),
767 missingAcceptableSensorNames.end(),
768 sensorName) ==
769 missingAcceptableSensorNames.end())
770 {
771 std::cerr
772 << "Pid controller: Missing a missing-unacceptable sensor from D-Bus "
773 << sensorName << "\n";
774 std::string inputSensorName =
775 sensorNameToDbusName(sensorName);
776 auto& config = sensorConfig[inputSensorName];
777 archivedInputSensorNames.push_back(inputSensorName);
778 config.type = pidClass;
779 config.readPath =
780 getSensorPath(config.type, inputSensorName);
781 config.timeout = 0;
782 config.ignoreDbusMinMax = true;
783 config.unavailableAsFailed = unavailableAsFailed;
784 }
785 else
786 {
787 // When an input sensor is NOT on DBus, and it's in
788 // the MissingIsAcceptable list. Ignore it and
789 // continue with the next input sensor.
790 std::cout
791 << "Pid controller: Missing a missing-acceptable sensor from D-Bus "
792 << sensorName << "\n";
793 continue;
794 }
795 }
796#endif
Jason Lingf3b04fd2020-07-24 09:33:04 -0700797 }
798 for (const std::string& sensorName : outputSensorNames)
799 {
800 findSensors(sensors, sensorNameToDbusName(sensorName),
801 outputSensorInterfaces);
James Feist1738e2a2019-02-04 15:57:03 -0800802 }
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800803 for (const std::string& sensorName :
804 missingAcceptableSensorNames)
805 {
806 findSensors(sensors, sensorNameToDbusName(sensorName),
807 missingAcceptableSensorInterfaces);
808 }
James Feist50fdfe32018-09-24 15:51:09 -0700809
Jason Lingf3b04fd2020-07-24 09:33:04 -0700810 for (const SensorInterfaceType& inputSensorInterface :
811 inputSensorInterfaces)
James Feist1738e2a2019-02-04 15:57:03 -0800812 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700813 const std::string& dbusInterface =
814 inputSensorInterface.second;
815 const std::string& inputSensorPath =
816 inputSensorInterface.first;
Josh Lehanfb82a872020-09-20 21:48:22 -0700817
818 // Setting timeout to 0 is intentional, as D-Bus passive
819 // sensor updates are pushed in, not pulled by timer poll.
820 // Setting ignoreDbusMinMax is intentional, as this
821 // prevents normalization of values to [0.0, 1.0] range,
822 // which would mess up the PID loop math.
823 // All non-fan PID classes should be initialized this way.
824 // As for why a fan should not use this code path, see
825 // the ed1dafdf168def37c65bfb7a5efd18d9dbe04727 commit.
Josh Lehan23e22b92022-11-12 22:37:58 -0800826 if ((pidClass == "temp") || (pidClass == "margin") ||
827 (pidClass == "power") || (pidClass == "powersum"))
James Feist50fdfe32018-09-24 15:51:09 -0700828 {
Harvey.Wued1dafd2022-02-09 13:53:20 +0800829 std::string inputSensorName =
830 getSensorNameFromPath(inputSensorPath);
831 auto& config = sensorConfig[inputSensorName];
Chaul Lya552fe22024-11-15 10:20:28 +0000832 archivedInputSensorNames.push_back(inputSensorName);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800833 config.type = pidClass;
834 config.readPath = inputSensorInterface.first;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700835 config.timeout = 0;
836 config.ignoreDbusMinMax = true;
Alex.Song8f73ad72021-10-07 00:18:27 +0800837 config.unavailableAsFailed = unavailableAsFailed;
James Feist50fdfe32018-09-24 15:51:09 -0700838 }
Josh Lehanfb82a872020-09-20 21:48:22 -0700839
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100840 if (dbusInterface != SensorValue::interface)
James Feist50fdfe32018-09-24 15:51:09 -0700841 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700842 /* all expected inputs in the configuration are expected
843 * to be sensor interfaces
844 */
Ed Tanous7d6e2252025-06-27 10:45:25 -0700845 throw std::runtime_error(std::format(
846 "sensor at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100847 inputSensorPath, dbusInterface,
848 SensorValue::interface));
James Feist50fdfe32018-09-24 15:51:09 -0700849 }
850 }
James Feist1738e2a2019-02-04 15:57:03 -0800851
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800852 // MissingIsAcceptable same postprocessing as Inputs
853 missingAcceptableSensorNames.clear();
854 for (const SensorInterfaceType&
855 missingAcceptableSensorInterface :
856 missingAcceptableSensorInterfaces)
857 {
858 const std::string& dbusInterface =
859 missingAcceptableSensorInterface.second;
860 const std::string& missingAcceptableSensorPath =
861 missingAcceptableSensorInterface.first;
862
863 std::string missingAcceptableSensorName =
864 getSensorNameFromPath(missingAcceptableSensorPath);
865 missingAcceptableSensorNames.push_back(
866 missingAcceptableSensorName);
867
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100868 if (dbusInterface != SensorValue::interface)
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800869 {
870 /* MissingIsAcceptable same error checking as Inputs
871 */
Ed Tanous7d6e2252025-06-27 10:45:25 -0700872 throw std::runtime_error(std::format(
873 "sensor at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
874 missingAcceptableSensorPath, dbusInterface,
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100875 SensorValue::interface));
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800876 }
877 }
878
Jason Lingf3b04fd2020-07-24 09:33:04 -0700879 /* fan pids need to pair up tach sensors with their pwm
880 * counterparts
881 */
882 if (pidClass == "fan")
883 {
884 /* If a PID is a fan there should be either
885 * (1) one output(pwm) per input(tach)
886 * OR
887 * (2) one putput(pwm) for all inputs(tach)
888 * everything else indicates a bad configuration.
889 */
890 bool singlePwm = false;
891 if (outputSensorInterfaces.size() == 1)
892 {
893 /* one pwm, set write paths for all fan sensors to it */
894 singlePwm = true;
895 }
896 else if (inputSensorInterfaces.size() ==
897 outputSensorInterfaces.size())
898 {
899 /* one to one mapping, each fan sensor gets its own pwm
900 * control */
901 singlePwm = false;
902 }
903 else
904 {
905 throw std::runtime_error(
906 "fan PID has invalid number of Outputs");
907 }
908 std::string fanSensorName;
909 std::string pwmPath;
910 std::string pwmInterface;
Harvey.Wued1dafd2022-02-09 13:53:20 +0800911 std::string pwmSensorName;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700912 if (singlePwm)
913 {
914 /* if just a single output(pwm) is provided then use
915 * that pwm control path for all the fan sensor write
916 * path configs
917 */
918 pwmPath = outputSensorInterfaces.at(0).first;
919 pwmInterface = outputSensorInterfaces.at(0).second;
920 }
921 for (uint32_t idx = 0; idx < inputSensorInterfaces.size();
922 idx++)
923 {
924 if (!singlePwm)
925 {
926 pwmPath = outputSensorInterfaces.at(idx).first;
927 pwmInterface =
928 outputSensorInterfaces.at(idx).second;
929 }
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100930 if (ControlFanPwm::interface != pwmInterface)
Jason Lingf3b04fd2020-07-24 09:33:04 -0700931 {
Ed Tanous7d6e2252025-06-27 10:45:25 -0700932 throw std::runtime_error(std::format(
933 "fan pwm control at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100934 pwmPath, pwmInterface,
935 ControlFanPwm::interface));
Jason Lingf3b04fd2020-07-24 09:33:04 -0700936 }
937 const std::string& fanPath =
938 inputSensorInterfaces.at(idx).first;
939 fanSensorName = getSensorNameFromPath(fanPath);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800940 pwmSensorName = getSensorNameFromPath(pwmPath);
941 std::string fanPwmIndex = fanSensorName + pwmSensorName;
Chaul Lya552fe22024-11-15 10:20:28 +0000942 archivedInputSensorNames.push_back(fanPwmIndex);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800943 auto& fanConfig = sensorConfig[fanPwmIndex];
944 fanConfig.type = pidClass;
945 fanConfig.readPath = fanPath;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700946 fanConfig.writePath = pwmPath;
947 // todo: un-hardcode this if there are fans with
948 // different ranges
949 fanConfig.max = 255;
950 fanConfig.min = 0;
951 }
952 }
James Feist11d243d2019-06-24 16:18:40 -0700953 // if the sensors aren't available in the current state, don't
954 // add them to the configuration.
Chaul Lya552fe22024-11-15 10:20:28 +0000955 if (archivedInputSensorNames.empty())
James Feist11d243d2019-06-24 16:18:40 -0700956 {
957 continue;
958 }
959
James Feist5ec20272019-07-10 11:59:57 -0700960 std::string offsetType;
James Feist50fdfe32018-09-24 15:51:09 -0700961
James Feist5ec20272019-07-10 11:59:57 -0700962 // SetPointOffset is a threshold value to pull from the sensor
963 // to apply an offset. For upper thresholds this means the
964 // setpoint is usually negative.
965 auto findSetpointOffset = base.find("SetPointOffset");
966 if (findSetpointOffset != base.end())
James Feist22c257a2018-08-31 14:07:12 -0700967 {
James Feist5ec20272019-07-10 11:59:57 -0700968 offsetType =
969 std::get<std::string>(findSetpointOffset->second);
970 if (std::find(thresholds::types.begin(),
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400971 thresholds::types.end(), offsetType) ==
972 thresholds::types.end())
James Feist5ec20272019-07-10 11:59:57 -0700973 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400974 throw std::runtime_error(
975 "Unsupported type: " + offsetType);
James Feist5ec20272019-07-10 11:59:57 -0700976 }
977 }
978
Josh Lehan31058fd2023-01-13 11:06:16 -0800979 std::vector<double> inputTempToMargin;
980
981 auto findTempToMargin = base.find("TempToMargin");
982 if (findTempToMargin != base.end())
983 {
984 inputTempToMargin =
985 std::get<std::vector<double>>(findTempToMargin->second);
986 }
987
988 std::vector<pid_control::conf::SensorInput> sensorInputs =
Chaul Lya552fe22024-11-15 10:20:28 +0000989 spliceInputs(archivedInputSensorNames, inputTempToMargin,
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800990 missingAcceptableSensorNames);
Josh Lehan31058fd2023-01-13 11:06:16 -0800991
James Feist5ec20272019-07-10 11:59:57 -0700992 if (offsetType.empty())
993 {
ykchiu7c6d35d2023-05-10 17:01:46 +0800994 conf::ControllerInfo& info = conf[pidName];
Josh Lehan31058fd2023-01-13 11:06:16 -0800995 info.inputs = std::move(sensorInputs);
Patrick Venture73823182020-10-08 15:12:51 -0700996 populatePidInfo(bus, base, info, nullptr, sensorConfig);
James Feist22c257a2018-08-31 14:07:12 -0700997 }
998 else
999 {
James Feist5ec20272019-07-10 11:59:57 -07001000 // we have to split up the inputs, as in practice t-control
1001 // values will differ, making setpoints differ
Josh Lehan31058fd2023-01-13 11:06:16 -08001002 for (const pid_control::conf::SensorInput& input :
1003 sensorInputs)
James Feist5ec20272019-07-10 11:59:57 -07001004 {
Josh Lehan31058fd2023-01-13 11:06:16 -08001005 conf::ControllerInfo& info = conf[input.name];
James Feist5ec20272019-07-10 11:59:57 -07001006 info.inputs.emplace_back(input);
Patrick Venture73823182020-10-08 15:12:51 -07001007 populatePidInfo(bus, base, info, &offsetType,
1008 sensorConfig);
James Feist5ec20272019-07-10 11:59:57 -07001009 }
James Feist22c257a2018-08-31 14:07:12 -07001010 }
James Feist22c257a2018-08-31 14:07:12 -07001011 }
1012 }
1013 auto findStepwise =
1014 configuration.second.find(stepwiseConfigurationInterface);
1015 if (findStepwise != configuration.second.end())
1016 {
1017 const auto& base = findStepwise->second;
ykchiu7c6d35d2023-05-10 17:01:46 +08001018 const std::string pidName =
1019 sensorNameToDbusName(std::get<std::string>(base.at("Name")));
James Feist22c257a2018-08-31 14:07:12 -07001020 const std::vector<std::string>& zones =
James Feist1f802f52019-02-08 13:51:43 -08001021 std::get<std::vector<std::string>>(base.at("Zones"));
James Feist22c257a2018-08-31 14:07:12 -07001022 for (const std::string& zone : zones)
1023 {
Josh Lehan998fbe62020-09-20 21:21:05 -07001024 auto index = getZoneIndex(zone, foundZones);
1025
James Feistf81f2882019-02-26 11:26:36 -08001026 conf::PIDConf& conf = zoneConfig[index];
James Feist50fdfe32018-09-24 15:51:09 -07001027
1028 std::vector<std::string> inputs;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001029 std::vector<std::string> missingAcceptableSensors;
1030 std::vector<std::string> missingAcceptableSensorNames;
James Feist50fdfe32018-09-24 15:51:09 -07001031 std::vector<std::string> sensorNames =
James Feist1f802f52019-02-08 13:51:43 -08001032 std::get<std::vector<std::string>>(base.at("Inputs"));
James Feist50fdfe32018-09-24 15:51:09 -07001033
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001034 auto findMissingAcceptable = base.find("MissingIsAcceptable");
1035 if (findMissingAcceptable != base.end())
1036 {
1037 missingAcceptableSensorNames =
1038 std::get<std::vector<std::string>>(
1039 findMissingAcceptable->second);
1040 }
1041
Alex.Song8f73ad72021-10-07 00:18:27 +08001042 bool unavailableAsFailed = true;
1043 auto findUnavailableAsFailed =
1044 base.find("InputUnavailableAsFailed");
1045 if (findUnavailableAsFailed != base.end())
1046 {
1047 unavailableAsFailed =
1048 std::get<bool>(findUnavailableAsFailed->second);
1049 }
1050
James Feist1738e2a2019-02-04 15:57:03 -08001051 bool sensorFound = false;
James Feist50fdfe32018-09-24 15:51:09 -07001052 for (const std::string& sensorName : sensorNames)
1053 {
James Feist1738e2a2019-02-04 15:57:03 -08001054 std::vector<std::pair<std::string, std::string>>
1055 sensorPathIfacePairs;
Jason Lingf3b04fd2020-07-24 09:33:04 -07001056 if (!findSensors(sensors, sensorNameToDbusName(sensorName),
1057 sensorPathIfacePairs))
James Feist50fdfe32018-09-24 15:51:09 -07001058 {
Chaul Lya552fe22024-11-15 10:20:28 +00001059#ifndef HANDLE_MISSING_OBJECT_PATHS
James Feist50fdfe32018-09-24 15:51:09 -07001060 break;
Chaul Lya552fe22024-11-15 10:20:28 +00001061#else
1062 if (std::find(missingAcceptableSensorNames.begin(),
1063 missingAcceptableSensorNames.end(),
1064 sensorName) ==
1065 missingAcceptableSensorNames.end())
1066 {
1067 // When an input sensor is NOT on DBus, and it's NOT
1068 // in the MissingIsAcceptable list. Build it as a
1069 // failed sensor with default information (temp
1070 // sensor path, temp type, ...)
1071 std::cerr
1072 << "Stepwise controller: Missing a missing-unacceptable sensor from D-Bus "
1073 << sensorName << "\n";
1074 std::string shortName =
1075 sensorNameToDbusName(sensorName);
1076
1077 inputs.push_back(shortName);
1078 auto& config = sensorConfig[shortName];
1079 config.type = "temp";
1080 config.readPath =
1081 getSensorPath(config.type, shortName);
1082 config.ignoreDbusMinMax = true;
1083 config.unavailableAsFailed = unavailableAsFailed;
1084 // todo: maybe un-hardcode this if we run into
1085 // slower timeouts with sensors
1086
1087 config.timeout = 0;
1088 sensorFound = true;
1089 }
1090 else
1091 {
1092 // When an input sensor is NOT on DBus, and it's in
1093 // the MissingIsAcceptable list. Ignore it and
1094 // continue with the next input sensor.
1095 std::cout
1096 << "Stepwise controller: Missing a missing-acceptable sensor from D-Bus "
1097 << sensorName << "\n";
1098 continue;
1099 }
1100#endif
James Feist50fdfe32018-09-24 15:51:09 -07001101 }
Chaul Lya552fe22024-11-15 10:20:28 +00001102 else
James Feist1738e2a2019-02-04 15:57:03 -08001103 {
Chaul Lya552fe22024-11-15 10:20:28 +00001104 for (const auto& sensorPathIfacePair :
1105 sensorPathIfacePairs)
1106 {
1107 std::string shortName = getSensorNameFromPath(
1108 sensorPathIfacePair.first);
James Feist50fdfe32018-09-24 15:51:09 -07001109
Chaul Lya552fe22024-11-15 10:20:28 +00001110 inputs.push_back(shortName);
1111 auto& config = sensorConfig[shortName];
1112 config.readPath = sensorPathIfacePair.first;
1113 config.type = "temp";
1114 config.ignoreDbusMinMax = true;
1115 config.unavailableAsFailed = unavailableAsFailed;
1116 // todo: maybe un-hardcode this if we run into
1117 // slower timeouts with sensors
James Feist1738e2a2019-02-04 15:57:03 -08001118
Chaul Lya552fe22024-11-15 10:20:28 +00001119 config.timeout = 0;
1120 sensorFound = true;
1121 }
James Feist1738e2a2019-02-04 15:57:03 -08001122 }
James Feist50fdfe32018-09-24 15:51:09 -07001123 }
1124 if (!sensorFound)
1125 {
1126 continue;
1127 }
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001128
1129 // MissingIsAcceptable same postprocessing as Inputs
1130 for (const std::string& missingAcceptableSensorName :
1131 missingAcceptableSensorNames)
1132 {
1133 std::vector<std::pair<std::string, std::string>>
1134 sensorPathIfacePairs;
1135 if (!findSensors(
1136 sensors,
1137 sensorNameToDbusName(missingAcceptableSensorName),
1138 sensorPathIfacePairs))
1139 {
Chaul Lya552fe22024-11-15 10:20:28 +00001140#ifndef HANDLE_MISSING_OBJECT_PATHS
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001141 break;
Chaul Lya552fe22024-11-15 10:20:28 +00001142#else
1143 // When a sensor in the MissingIsAcceptable list is NOT
1144 // on DBus and it still reaches here, which contradicts
1145 // to what we did in the Input sensor building step.
1146 // Continue.
1147 continue;
1148#endif
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001149 }
1150
1151 for (const auto& sensorPathIfacePair : sensorPathIfacePairs)
1152 {
1153 std::string shortName =
1154 getSensorNameFromPath(sensorPathIfacePair.first);
1155
1156 missingAcceptableSensors.push_back(shortName);
1157 }
1158 }
1159
ykchiu7c6d35d2023-05-10 17:01:46 +08001160 conf::ControllerInfo& info = conf[pidName];
Josh Lehan31058fd2023-01-13 11:06:16 -08001161
1162 std::vector<double> inputTempToMargin;
1163
1164 auto findTempToMargin = base.find("TempToMargin");
1165 if (findTempToMargin != base.end())
1166 {
1167 inputTempToMargin =
1168 std::get<std::vector<double>>(findTempToMargin->second);
1169 }
1170
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001171 info.inputs = spliceInputs(inputs, inputTempToMargin,
1172 missingAcceptableSensors);
James Feist50fdfe32018-09-24 15:51:09 -07001173
James Feist22c257a2018-08-31 14:07:12 -07001174 info.type = "stepwise";
1175 info.stepwiseInfo.ts = 1.0; // currently unused
James Feist3dfaafd2018-09-20 15:46:58 -07001176 info.stepwiseInfo.positiveHysteresis = 0.0;
1177 info.stepwiseInfo.negativeHysteresis = 0.0;
James Feist608304d2019-02-25 10:01:42 -08001178 std::string subtype = std::get<std::string>(base.at("Class"));
1179
1180 info.stepwiseInfo.isCeiling = (subtype == "Ceiling");
James Feist3dfaafd2018-09-20 15:46:58 -07001181 auto findPosHyst = base.find("PositiveHysteresis");
1182 auto findNegHyst = base.find("NegativeHysteresis");
1183 if (findPosHyst != base.end())
1184 {
James Feist1f802f52019-02-08 13:51:43 -08001185 info.stepwiseInfo.positiveHysteresis = std::visit(
James Feist208abce2018-12-06 09:59:10 -08001186 VariantToDoubleVisitor(), findPosHyst->second);
James Feist3dfaafd2018-09-20 15:46:58 -07001187 }
1188 if (findNegHyst != base.end())
1189 {
James Feist5782ab82019-04-02 08:38:48 -07001190 info.stepwiseInfo.negativeHysteresis = std::visit(
James Feist208abce2018-12-06 09:59:10 -08001191 VariantToDoubleVisitor(), findNegHyst->second);
James Feist3dfaafd2018-09-20 15:46:58 -07001192 }
James Feist22c257a2018-08-31 14:07:12 -07001193 std::vector<double> readings =
James Feist1f802f52019-02-08 13:51:43 -08001194 std::get<std::vector<double>>(base.at("Reading"));
James Feist22c257a2018-08-31 14:07:12 -07001195 if (readings.size() > ec::maxStepwisePoints)
1196 {
1197 throw std::invalid_argument("Too many stepwise points.");
1198 }
1199 if (readings.empty())
1200 {
1201 throw std::invalid_argument(
1202 "Must have one stepwise point.");
1203 }
1204 std::copy(readings.begin(), readings.end(),
1205 info.stepwiseInfo.reading);
1206 if (readings.size() < ec::maxStepwisePoints)
1207 {
1208 info.stepwiseInfo.reading[readings.size()] =
Patrick Venture5f59c0f2018-11-11 12:55:14 -08001209 std::numeric_limits<double>::quiet_NaN();
James Feist22c257a2018-08-31 14:07:12 -07001210 }
1211 std::vector<double> outputs =
James Feist1f802f52019-02-08 13:51:43 -08001212 std::get<std::vector<double>>(base.at("Output"));
James Feist22c257a2018-08-31 14:07:12 -07001213 if (readings.size() != outputs.size())
1214 {
1215 throw std::invalid_argument(
1216 "Outputs size must match readings");
1217 }
1218 std::copy(outputs.begin(), outputs.end(),
1219 info.stepwiseInfo.output);
1220 if (outputs.size() < ec::maxStepwisePoints)
1221 {
1222 info.stepwiseInfo.output[outputs.size()] =
Patrick Venture5f59c0f2018-11-11 12:55:14 -08001223 std::numeric_limits<double>::quiet_NaN();
James Feist22c257a2018-08-31 14:07:12 -07001224 }
James Feist7136a5a2018-07-19 09:52:05 -07001225 }
1226 }
1227 }
Patrick Venture39199b42020-10-08 14:40:29 -07001228 if constexpr (pid_control::conf::DEBUG)
James Feist7136a5a2018-07-19 09:52:05 -07001229 {
Patrick Venture39199b42020-10-08 14:40:29 -07001230 debugPrint(sensorConfig, zoneConfig, zoneDetailsConfig);
James Feist7136a5a2018-07-19 09:52:05 -07001231 }
James Feistc959c422018-11-01 12:33:40 -07001232 if (zoneConfig.empty() || zoneDetailsConfig.empty())
James Feist50fdfe32018-09-24 15:51:09 -07001233 {
James Feist1fe08952019-05-07 09:17:16 -07001234 std::cerr
1235 << "No fan zones, application pausing until new configuration\n";
1236 return false;
James Feist50fdfe32018-09-24 15:51:09 -07001237 }
James Feist1fe08952019-05-07 09:17:16 -07001238 return true;
James Feist7136a5a2018-07-19 09:52:05 -07001239}
Patrick Venturea0764872020-08-08 07:48:43 -07001240
James Feist7136a5a2018-07-19 09:52:05 -07001241} // namespace dbus_configuration
Patrick Venturea0764872020-08-08 07:48:43 -07001242} // namespace pid_control