blob: 2814b65f619e5de402db0a6d51dc8460bb0bc362 [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 Hansend3e71ce2025-11-12 16:20:53 +010038#include <xyz/openbmc_project/Sensor/Threshold/Critical/common.hpp>
39#include <xyz/openbmc_project/Sensor/Threshold/Warning/common.hpp>
Alexander Hansendae4a3a2025-11-11 17:12:01 +010040#include <xyz/openbmc_project/Sensor/Value/client.hpp>
Patrick Venturea83a3ec2020-08-04 09:52:05 -070041
42#include <algorithm>
Ed Tanousf8b6e552025-06-27 13:27:50 -070043#include <array>
James Feist64f072a2018-08-10 16:39:24 -070044#include <chrono>
Ed Tanousf8b6e552025-06-27 13:27:50 -070045#include <cstdint>
46#include <format>
James Feist7136a5a2018-07-19 09:52:05 -070047#include <iostream>
Ed Tanousf8b6e552025-06-27 13:27:50 -070048#include <limits>
James Feist1fe08952019-05-07 09:17:16 -070049#include <list>
Ed Tanousf8b6e552025-06-27 13:27:50 -070050#include <map>
51#include <stdexcept>
52#include <string>
53#include <tuple>
James Feist7136a5a2018-07-19 09:52:05 -070054#include <unordered_map>
Ed Tanousf8b6e552025-06-27 13:27:50 -070055#include <utility>
James Feist1f802f52019-02-08 13:51:43 -080056#include <variant>
Ed Tanousf8b6e552025-06-27 13:27:50 -070057#include <vector>
James Feist7136a5a2018-07-19 09:52:05 -070058
Alexander Hansen1ec3d132025-10-27 17:19:50 +010059using ObjectMapper = sdbusplus::common::xyz::openbmc_project::ObjectMapper;
Alexander Hansendae4a3a2025-11-11 17:12:01 +010060using SensorValue = sdbusplus::common::xyz::openbmc_project::sensor::Value;
Alexander Hansenbd2c98e2025-11-12 12:47:00 +010061using ControlFanPwm = sdbusplus::common::xyz::openbmc_project::control::FanPwm;
Alexander Hansend7665432025-11-12 14:01:48 +010062using ControlThermalMode =
63 sdbusplus::common::xyz::openbmc_project::control::ThermalMode;
Alexander Hansend3e71ce2025-11-12 16:20:53 +010064using SensorThresholdWarning =
65 sdbusplus::common::xyz::openbmc_project::sensor::threshold::Warning;
66using SensorThresholdCritical =
67 sdbusplus::common::xyz::openbmc_project::sensor::threshold::Critical;
Alexander Hansen1ec3d132025-10-27 17:19:50 +010068
Patrick Venturea0764872020-08-08 07:48:43 -070069namespace pid_control
70{
71
Patrick Venturee2ec0f62018-09-04 12:30:27 -070072constexpr const char* pidConfigurationInterface =
James Feist7136a5a2018-07-19 09:52:05 -070073 "xyz.openbmc_project.Configuration.Pid";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070074constexpr const char* objectManagerInterface =
James Feist7136a5a2018-07-19 09:52:05 -070075 "org.freedesktop.DBus.ObjectManager";
Patrick Venturee2ec0f62018-09-04 12:30:27 -070076constexpr const char* pidZoneConfigurationInterface =
James Feist7136a5a2018-07-19 09:52:05 -070077 "xyz.openbmc_project.Configuration.Pid.Zone";
James Feist22c257a2018-08-31 14:07:12 -070078constexpr const char* stepwiseConfigurationInterface =
79 "xyz.openbmc_project.Configuration.Stepwise";
James Feist7136a5a2018-07-19 09:52:05 -070080
James Feist991ebd82020-07-21 11:14:52 -070081using Association = std::tuple<std::string, std::string, std::string>;
82using Associations = std::vector<Association>;
83
James Feist5ec20272019-07-10 11:59:57 -070084namespace thresholds
85{
James Feist5ec20272019-07-10 11:59:57 -070086const std::array<const char*, 4> types = {"CriticalLow", "CriticalHigh",
87 "WarningLow", "WarningHigh"};
88
89} // namespace thresholds
90
James Feist7136a5a2018-07-19 09:52:05 -070091namespace dbus_configuration
92{
Jason Lingf3b04fd2020-07-24 09:33:04 -070093using SensorInterfaceType = std::pair<std::string, std::string>;
94
95inline std::string getSensorNameFromPath(const std::string& dbusPath)
96{
Ed Tanousd2768c52025-06-26 11:42:57 -070097 return dbusPath.substr(dbusPath.find_last_of('/') + 1);
Jason Lingf3b04fd2020-07-24 09:33:04 -070098}
99
100inline std::string sensorNameToDbusName(const std::string& sensorName)
101{
102 std::string retString = sensorName;
103 std::replace(retString.begin(), retString.end(), ' ', '_');
104 return retString;
105}
James Feist5ec20272019-07-10 11:59:57 -0700106
Patrick Williamsb228bc32022-07-22 19:26:56 -0500107std::vector<std::string> getSelectedProfiles(sdbusplus::bus_t& bus)
James Feistf0096a02019-02-21 11:25:22 -0800108{
109 std::vector<std::string> ret;
Alexander Hansen1ec3d132025-10-27 17:19:50 +0100110 auto mapper = bus.new_method_call(
111 ObjectMapper::default_service, ObjectMapper::instance_path,
112 ObjectMapper::interface, ObjectMapper::method_names::get_sub_tree);
Alexander Hansend7665432025-11-12 14:01:48 +0100113 mapper.append("/", 0,
114 std::array<const char*, 1>{ControlThermalMode::interface});
James Feistf0096a02019-02-21 11:25:22 -0800115 std::unordered_map<
116 std::string, std::unordered_map<std::string, std::vector<std::string>>>
117 respData;
118
119 try
120 {
121 auto resp = bus.call(mapper);
122 resp.read(respData);
123 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500124 catch (const sdbusplus::exception_t&)
James Feistf0096a02019-02-21 11:25:22 -0800125 {
126 // can't do anything without mapper call data
127 throw std::runtime_error("ObjectMapper Call Failure");
128 }
129 if (respData.empty())
130 {
131 // if the user has profiles but doesn't expose the interface to select
132 // one, just go ahead without using profiles
133 return ret;
134 }
135
136 // assumption is that we should only have a small handful of selected
137 // profiles at a time (probably only 1), so calling each individually should
138 // not incur a large cost
139 for (const auto& objectPair : respData)
140 {
141 const std::string& path = objectPair.first;
142 for (const auto& ownerPair : objectPair.second)
143 {
144 const std::string& busName = ownerPair.first;
145 auto getProfile =
146 bus.new_method_call(busName.c_str(), path.c_str(),
147 "org.freedesktop.DBus.Properties", "Get");
Alexander Hansend7665432025-11-12 14:01:48 +0100148 getProfile.append(ControlThermalMode::interface,
149 ControlThermalMode::property_names::current);
James Feistf0096a02019-02-21 11:25:22 -0800150 std::variant<std::string> variantResp;
151 try
152 {
153 auto resp = bus.call(getProfile);
154 resp.read(variantResp);
155 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500156 catch (const sdbusplus::exception_t&)
James Feistf0096a02019-02-21 11:25:22 -0800157 {
158 throw std::runtime_error("Failure getting profile");
159 }
160 std::string mode = std::get<std::string>(variantResp);
161 ret.emplace_back(std::move(mode));
162 }
163 }
Patrick Venture39199b42020-10-08 14:40:29 -0700164 if constexpr (pid_control::conf::DEBUG)
James Feistf0096a02019-02-21 11:25:22 -0800165 {
166 std::cout << "Profiles selected: ";
167 for (const auto& profile : ret)
168 {
169 std::cout << profile << " ";
170 }
171 std::cout << "\n";
172 }
173 return ret;
174}
175
James Feist991ebd82020-07-21 11:14:52 -0700176int eventHandler(sd_bus_message* m, void* context, sd_bus_error*)
James Feist7136a5a2018-07-19 09:52:05 -0700177{
James Feist991ebd82020-07-21 11:14:52 -0700178 if (context == nullptr || m == nullptr)
James Feist1fe08952019-05-07 09:17:16 -0700179 {
180 throw std::runtime_error("Invalid match");
181 }
James Feist991ebd82020-07-21 11:14:52 -0700182
183 // we skip associations because the mapper populates these, not the sensors
Josh Lehan10e46ef2023-02-01 18:25:58 -0800184 const std::array<const char*, 2> skipList = {
185 "xyz.openbmc_project.Association",
186 "xyz.openbmc_project.Association.Definitions"};
James Feist991ebd82020-07-21 11:14:52 -0700187
Patrick Williamsb228bc32022-07-22 19:26:56 -0500188 sdbusplus::message_t message(m);
James Feist991ebd82020-07-21 11:14:52 -0700189 if (std::string(message.get_member()) == "InterfacesAdded")
190 {
191 sdbusplus::message::object_path path;
192 std::unordered_map<
193 std::string,
194 std::unordered_map<std::string, std::variant<Associations, bool>>>
195 data;
196
197 message.read(path, data);
198
199 for (const char* skip : skipList)
200 {
201 auto find = data.find(skip);
202 if (find != data.end())
203 {
204 data.erase(find);
205 if (data.empty())
206 {
207 return 1;
208 }
209 }
210 }
Josh Lehan10e46ef2023-02-01 18:25:58 -0800211
212 if constexpr (pid_control::conf::DEBUG)
213 {
214 std::cout << "New config detected: " << path.str << std::endl;
215 for (auto& d : data)
216 {
217 std::cout << "\tdata is " << d.first << std::endl;
218 for (auto& second : d.second)
219 {
220 std::cout << "\t\tdata is " << second.first << std::endl;
221 }
222 }
223 }
James Feist991ebd82020-07-21 11:14:52 -0700224 }
225
James Feist1fe08952019-05-07 09:17:16 -0700226 boost::asio::steady_timer* timer =
227 static_cast<boost::asio::steady_timer*>(context);
228
229 // do a brief sleep as we tend to get a bunch of these events at
230 // once
231 timer->expires_after(std::chrono::seconds(2));
232 timer->async_wait([](const boost::system::error_code ec) {
233 if (ec == boost::asio::error::operation_aborted)
234 {
235 /* another timer started*/
236 return;
237 }
238
239 std::cout << "New configuration detected, reloading\n.";
Yong Li298a95c2020-04-07 15:11:02 +0800240 tryRestartControlLoops();
James Feist1fe08952019-05-07 09:17:16 -0700241 });
242
243 return 1;
244}
245
Patrick Williamsb228bc32022-07-22 19:26:56 -0500246void createMatches(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer)
James Feist1fe08952019-05-07 09:17:16 -0700247{
248 // this is a list because the matches can't be moved
Patrick Williamsb228bc32022-07-22 19:26:56 -0500249 static std::list<sdbusplus::bus::match_t> matches;
James Feist1fe08952019-05-07 09:17:16 -0700250
James Feist3987c8b2019-05-13 10:43:17 -0700251 const std::array<std::string, 4> interfaces = {
Alexander Hansend7665432025-11-12 14:01:48 +0100252 ControlThermalMode::interface, pidConfigurationInterface,
James Feist3987c8b2019-05-13 10:43:17 -0700253 pidZoneConfigurationInterface, stepwiseConfigurationInterface};
James Feist1fe08952019-05-07 09:17:16 -0700254
255 // this list only needs to be created once
256 if (!matches.empty())
257 {
258 return;
259 }
260
261 // we restart when the configuration changes or there are new sensors
262 for (const auto& interface : interfaces)
263 {
264 matches.emplace_back(
265 bus,
266 "type='signal',member='PropertiesChanged',arg0namespace='" +
267 interface + "'",
268 eventHandler, &timer);
269 }
270 matches.emplace_back(
271 bus,
272 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/"
273 "sensors/'",
274 eventHandler, &timer);
Jinliang Wangc2a311b2023-04-26 18:36:56 +0000275 matches.emplace_back(bus,
276 "type='signal',member='InterfacesRemoved',arg0path='/"
277 "xyz/openbmc_project/sensors/'",
278 eventHandler, &timer);
James Feist1fe08952019-05-07 09:17:16 -0700279}
280
Jason Ling6fc301f2020-07-23 12:39:57 -0700281/**
282 * retrieve an attribute from the pid configuration map
283 * @param[in] base - the PID configuration map, keys are the attributes and
284 * value is the variant associated with that attribute.
285 * @param attributeName - the name of the attribute
286 * @return a variant holding the value associated with a key
287 * @throw runtime_error : attributeName is not in base
288 */
289inline DbusVariantType getPIDAttribute(
290 const std::unordered_map<std::string, DbusVariantType>& base,
291 const std::string& attributeName)
292{
293 auto search = base.find(attributeName);
294 if (search == base.end())
295 {
296 throw std::runtime_error("missing attribute " + attributeName);
297 }
298 return search->second;
299}
300
Harvey Wu239aa7d2022-11-18 08:43:34 +0800301inline void getCycleTimeSetting(
302 const std::unordered_map<std::string, DbusVariantType>& zone,
303 const int zoneIndex, const std::string& attributeName, uint64_t& value)
304{
305 auto findAttributeName = zone.find(attributeName);
306 if (findAttributeName != zone.end())
307 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400308 double tmpAttributeValue =
309 std::visit(VariantToDoubleVisitor(), zone.at(attributeName));
Harvey Wu239aa7d2022-11-18 08:43:34 +0800310 if (tmpAttributeValue >= 1.0)
311 {
312 value = static_cast<uint64_t>(tmpAttributeValue);
313 }
314 else
315 {
316 std::cerr << "Zone " << zoneIndex << ": " << attributeName
317 << " is invalid. Use default " << value << " ms\n";
318 }
319 }
320 else
321 {
322 std::cerr << "Zone " << zoneIndex << ": " << attributeName
323 << " cannot find setting. Use default " << value << " ms\n";
324 }
325}
326
James Feist5ec20272019-07-10 11:59:57 -0700327void populatePidInfo(
Patrick Williamscd1e78a2025-04-07 17:21:05 -0400328 sdbusplus::bus_t& bus,
James Feist5ec20272019-07-10 11:59:57 -0700329 const std::unordered_map<std::string, DbusVariantType>& base,
Patrick Venture1df9e872020-10-08 15:35:01 -0700330 conf::ControllerInfo& info, const std::string* thresholdProperty,
Patrick Venture73823182020-10-08 15:12:51 -0700331 const std::map<std::string, conf::SensorConfig>& sensorConfig)
James Feist5ec20272019-07-10 11:59:57 -0700332{
Jason Ling6fc301f2020-07-23 12:39:57 -0700333 info.type = std::get<std::string>(getPIDAttribute(base, "Class"));
James Feist5ec20272019-07-10 11:59:57 -0700334 if (info.type == "fan")
335 {
336 info.setpoint = 0;
337 }
338 else
339 {
Jason Ling6fc301f2020-07-23 12:39:57 -0700340 info.setpoint = std::visit(VariantToDoubleVisitor(),
341 getPIDAttribute(base, "SetPoint"));
James Feist5ec20272019-07-10 11:59:57 -0700342 }
343
ykchiu9fe3a3c2023-05-11 13:43:54 +0800344 int failsafepercent = 0;
345 auto findFailSafe = base.find("FailSafePercent");
346 if (findFailSafe != base.end())
347 {
348 failsafepercent = std::visit(VariantToDoubleVisitor(),
349 getPIDAttribute(base, "FailSafePercent"));
350 }
351 info.failSafePercent = failsafepercent;
352
James Feist5ec20272019-07-10 11:59:57 -0700353 if (thresholdProperty != nullptr)
354 {
355 std::string interface;
Alexander Hansend3e71ce2025-11-12 16:20:53 +0100356 if (*thresholdProperty ==
357 SensorThresholdWarning::property_names::warning_high ||
358 *thresholdProperty ==
359 SensorThresholdWarning::property_names::warning_low)
James Feist5ec20272019-07-10 11:59:57 -0700360 {
Alexander Hansend3e71ce2025-11-12 16:20:53 +0100361 interface = SensorThresholdWarning::interface;
James Feist5ec20272019-07-10 11:59:57 -0700362 }
363 else
364 {
Alexander Hansend3e71ce2025-11-12 16:20:53 +0100365 interface = SensorThresholdCritical::interface;
James Feist5ec20272019-07-10 11:59:57 -0700366 }
Josh Lehan31058fd2023-01-13 11:06:16 -0800367
368 // Although this checks only the first vector element for the
369 // named threshold, it is OK, because the SetPointOffset parser
370 // splits up the input into individual vectors, each with only a
371 // single element, if it detects that SetPointOffset is in use.
372 const std::string& path =
373 sensorConfig.at(info.inputs.front().name).readPath;
James Feist5ec20272019-07-10 11:59:57 -0700374
Patrick Williamscd1e78a2025-04-07 17:21:05 -0400375 DbusHelper helper(bus);
Patrick Venture9b936922020-08-10 11:28:39 -0700376 std::string service = helper.getService(interface, path);
James Feist5ec20272019-07-10 11:59:57 -0700377 double reading = 0;
378 try
379 {
Patrick Venture9b936922020-08-10 11:28:39 -0700380 helper.getProperty(service, path, interface, *thresholdProperty,
381 reading);
James Feist5ec20272019-07-10 11:59:57 -0700382 }
Patrick Williamsb228bc32022-07-22 19:26:56 -0500383 catch (const sdbusplus::exception_t& ex)
James Feist5ec20272019-07-10 11:59:57 -0700384 {
385 // unsupported threshold, leaving reading at 0
386 }
387
388 info.setpoint += reading;
389 }
390
391 info.pidInfo.ts = 1.0; // currently unused
Jason Ling6fc301f2020-07-23 12:39:57 -0700392 info.pidInfo.proportionalCoeff = std::visit(
393 VariantToDoubleVisitor(), getPIDAttribute(base, "PCoefficient"));
394 info.pidInfo.integralCoeff = std::visit(
395 VariantToDoubleVisitor(), getPIDAttribute(base, "ICoefficient"));
Josh Lehanc612c052022-12-12 09:56:47 -0800396 // DCoefficient is below, it is optional, same reason as in buildjson.cpp
Jason Ling6fc301f2020-07-23 12:39:57 -0700397 info.pidInfo.feedFwdOffset = std::visit(
398 VariantToDoubleVisitor(), getPIDAttribute(base, "FFOffCoefficient"));
399 info.pidInfo.feedFwdGain = std::visit(
400 VariantToDoubleVisitor(), getPIDAttribute(base, "FFGainCoefficient"));
401 info.pidInfo.integralLimit.max = std::visit(
402 VariantToDoubleVisitor(), getPIDAttribute(base, "ILimitMax"));
403 info.pidInfo.integralLimit.min = std::visit(
404 VariantToDoubleVisitor(), getPIDAttribute(base, "ILimitMin"));
405 info.pidInfo.outLim.max = std::visit(VariantToDoubleVisitor(),
406 getPIDAttribute(base, "OutLimitMax"));
407 info.pidInfo.outLim.min = std::visit(VariantToDoubleVisitor(),
408 getPIDAttribute(base, "OutLimitMin"));
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400409 info.pidInfo.slewNeg =
410 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewNeg"));
411 info.pidInfo.slewPos =
412 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewPos"));
Josh Lehanc612c052022-12-12 09:56:47 -0800413
Delphine CC Chiu97889632023-11-06 11:32:46 +0800414 bool checkHysterWithSetpt = false;
James Feist5ec20272019-07-10 11:59:57 -0700415 double negativeHysteresis = 0;
416 double positiveHysteresis = 0;
Josh Lehanc612c052022-12-12 09:56:47 -0800417 double derivativeCoeff = 0;
James Feist5ec20272019-07-10 11:59:57 -0700418
Delphine CC Chiu5d897e22024-06-04 13:33:22 +0800419 auto findCheckHysterFlag = base.find("CheckHysteresisWithSetpoint");
James Feist5ec20272019-07-10 11:59:57 -0700420 auto findNeg = base.find("NegativeHysteresis");
421 auto findPos = base.find("PositiveHysteresis");
Josh Lehanc612c052022-12-12 09:56:47 -0800422 auto findDerivative = base.find("DCoefficient");
James Feist5ec20272019-07-10 11:59:57 -0700423
Delphine CC Chiu97889632023-11-06 11:32:46 +0800424 if (findCheckHysterFlag != base.end())
425 {
426 checkHysterWithSetpt = std::get<bool>(findCheckHysterFlag->second);
427 }
James Feist5ec20272019-07-10 11:59:57 -0700428 if (findNeg != base.end())
429 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400430 negativeHysteresis =
431 std::visit(VariantToDoubleVisitor(), findNeg->second);
James Feist5ec20272019-07-10 11:59:57 -0700432 }
James Feist5ec20272019-07-10 11:59:57 -0700433 if (findPos != base.end())
434 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400435 positiveHysteresis =
436 std::visit(VariantToDoubleVisitor(), findPos->second);
James Feist5ec20272019-07-10 11:59:57 -0700437 }
Josh Lehanc612c052022-12-12 09:56:47 -0800438 if (findDerivative != base.end())
439 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400440 derivativeCoeff =
441 std::visit(VariantToDoubleVisitor(), findDerivative->second);
Josh Lehanc612c052022-12-12 09:56:47 -0800442 }
443
Delphine CC Chiu97889632023-11-06 11:32:46 +0800444 info.pidInfo.checkHysterWithSetpt = checkHysterWithSetpt;
James Feist5ec20272019-07-10 11:59:57 -0700445 info.pidInfo.negativeHysteresis = negativeHysteresis;
446 info.pidInfo.positiveHysteresis = positiveHysteresis;
Josh Lehanc612c052022-12-12 09:56:47 -0800447 info.pidInfo.derivativeCoeff = derivativeCoeff;
James Feist5ec20272019-07-10 11:59:57 -0700448}
449
Patrick Williamsb228bc32022-07-22 19:26:56 -0500450bool init(sdbusplus::bus_t& bus, boost::asio::steady_timer& timer,
Patrick Venture73823182020-10-08 15:12:51 -0700451 std::map<std::string, conf::SensorConfig>& sensorConfig,
452 std::map<int64_t, conf::PIDConf>& zoneConfig,
453 std::map<int64_t, conf::ZoneConfig>& zoneDetailsConfig)
James Feist1fe08952019-05-07 09:17:16 -0700454{
James Feist1fe08952019-05-07 09:17:16 -0700455 sensorConfig.clear();
456 zoneConfig.clear();
457 zoneDetailsConfig.clear();
458
459 createMatches(bus, timer);
460
Alexander Hansen1ec3d132025-10-27 17:19:50 +0100461 auto mapper = bus.new_method_call(
462 ObjectMapper::default_service, ObjectMapper::instance_path,
463 ObjectMapper::interface, ObjectMapper::method_names::get_sub_tree);
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400464 mapper.append(
465 "/", 0,
466 std::array<const char*, 6>{
467 objectManagerInterface, pidConfigurationInterface,
468 pidZoneConfigurationInterface, stepwiseConfigurationInterface,
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100469 SensorValue::interface, ControlFanPwm::interface});
James Feist7136a5a2018-07-19 09:52:05 -0700470 std::unordered_map<
471 std::string, std::unordered_map<std::string, std::vector<std::string>>>
472 respData;
James Feist22c257a2018-08-31 14:07:12 -0700473 try
474 {
475 auto resp = bus.call(mapper);
James Feist22c257a2018-08-31 14:07:12 -0700476 resp.read(respData);
477 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500478 catch (const sdbusplus::exception_t&)
James Feist22c257a2018-08-31 14:07:12 -0700479 {
480 // can't do anything without mapper call data
481 throw std::runtime_error("ObjectMapper Call Failure");
482 }
James Feist7136a5a2018-07-19 09:52:05 -0700483
James Feist7136a5a2018-07-19 09:52:05 -0700484 if (respData.empty())
485 {
James Feist22c257a2018-08-31 14:07:12 -0700486 // can't do anything without mapper call data
James Feist7136a5a2018-07-19 09:52:05 -0700487 throw std::runtime_error("No configuration data available from Mapper");
488 }
489 // create a map of pair of <has pid configuration, ObjectManager path>
490 std::unordered_map<std::string, std::pair<bool, std::string>> owners;
491 // and a map of <path, interface> for sensors
492 std::unordered_map<std::string, std::string> sensors;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700493 for (const auto& objectPair : respData)
James Feist7136a5a2018-07-19 09:52:05 -0700494 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700495 for (const auto& ownerPair : objectPair.second)
James Feist7136a5a2018-07-19 09:52:05 -0700496 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700497 auto& owner = owners[ownerPair.first];
498 for (const std::string& interface : ownerPair.second)
James Feist7136a5a2018-07-19 09:52:05 -0700499 {
James Feist7136a5a2018-07-19 09:52:05 -0700500 if (interface == objectManagerInterface)
501 {
502 owner.second = objectPair.first;
503 }
504 if (interface == pidConfigurationInterface ||
James Feist22c257a2018-08-31 14:07:12 -0700505 interface == pidZoneConfigurationInterface ||
506 interface == stepwiseConfigurationInterface)
James Feist7136a5a2018-07-19 09:52:05 -0700507 {
508 owner.first = true;
509 }
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100510 if (interface == SensorValue::interface ||
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100511 interface == ControlFanPwm::interface)
James Feist7136a5a2018-07-19 09:52:05 -0700512 {
513 // we're not interested in pwm sensors, just pwm control
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100514 if (interface == SensorValue::interface &&
James Feist7136a5a2018-07-19 09:52:05 -0700515 objectPair.first.find("pwm") != std::string::npos)
516 {
517 continue;
518 }
519 sensors[objectPair.first] = interface;
520 }
521 }
522 }
523 }
524 ManagedObjectType configurations;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700525 for (const auto& owner : owners)
James Feist7136a5a2018-07-19 09:52:05 -0700526 {
527 // skip if no pid configuration (means probably a sensor)
528 if (!owner.second.first)
529 {
530 continue;
531 }
532 auto endpoint = bus.new_method_call(
533 owner.first.c_str(), owner.second.second.c_str(),
534 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
James Feist22c257a2018-08-31 14:07:12 -0700535 ManagedObjectType configuration;
536 try
James Feist7136a5a2018-07-19 09:52:05 -0700537 {
Manojkiran Eda7ca88872024-06-17 11:55:48 +0530538 auto response = bus.call(endpoint);
539 response.read(configuration);
James Feist22c257a2018-08-31 14:07:12 -0700540 }
Patrick Williams0001ee02021-10-06 14:44:22 -0500541 catch (const sdbusplus::exception_t&)
James Feist22c257a2018-08-31 14:07:12 -0700542 {
543 // this shouldn't happen, probably means daemon crashed
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400544 throw std::runtime_error(
545 "Error getting managed objects from " + owner.first);
James Feist7136a5a2018-07-19 09:52:05 -0700546 }
James Feist22c257a2018-08-31 14:07:12 -0700547
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700548 for (auto& pathPair : configuration)
James Feist7136a5a2018-07-19 09:52:05 -0700549 {
550 if (pathPair.second.find(pidConfigurationInterface) !=
551 pathPair.second.end() ||
552 pathPair.second.find(pidZoneConfigurationInterface) !=
James Feist22c257a2018-08-31 14:07:12 -0700553 pathPair.second.end() ||
554 pathPair.second.find(stepwiseConfigurationInterface) !=
James Feist7136a5a2018-07-19 09:52:05 -0700555 pathPair.second.end())
556 {
557 configurations.emplace(pathPair);
558 }
James Feistf0096a02019-02-21 11:25:22 -0800559 }
560 }
561
562 // remove controllers from config that aren't in the current profile(s)
James Feist3987c8b2019-05-13 10:43:17 -0700563 std::vector<std::string> selectedProfiles = getSelectedProfiles(bus);
564 if (selectedProfiles.size())
James Feistf0096a02019-02-21 11:25:22 -0800565 {
James Feist3987c8b2019-05-13 10:43:17 -0700566 for (auto pathIt = configurations.begin();
567 pathIt != configurations.end();)
James Feistf0096a02019-02-21 11:25:22 -0800568 {
James Feist3987c8b2019-05-13 10:43:17 -0700569 for (auto confIt = pathIt->second.begin();
570 confIt != pathIt->second.end();)
James Feistf0096a02019-02-21 11:25:22 -0800571 {
James Feist3987c8b2019-05-13 10:43:17 -0700572 auto profilesFind = confIt->second.find("Profiles");
573 if (profilesFind == confIt->second.end())
James Feistf0096a02019-02-21 11:25:22 -0800574 {
James Feist3987c8b2019-05-13 10:43:17 -0700575 confIt++;
576 continue; // if no profiles selected, apply always
577 }
578 auto profiles =
579 std::get<std::vector<std::string>>(profilesFind->second);
580 if (profiles.empty())
581 {
582 confIt++;
583 continue;
584 }
585
586 bool found = false;
587 for (const std::string& profile : profiles)
588 {
589 if (std::find(selectedProfiles.begin(),
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400590 selectedProfiles.end(), profile) !=
591 selectedProfiles.end())
James Feist3987c8b2019-05-13 10:43:17 -0700592 {
593 found = true;
594 break;
595 }
596 }
597 if (found)
598 {
599 confIt++;
James Feistf0096a02019-02-21 11:25:22 -0800600 }
601 else
602 {
James Feist3987c8b2019-05-13 10:43:17 -0700603 confIt = pathIt->second.erase(confIt);
James Feistf0096a02019-02-21 11:25:22 -0800604 }
605 }
James Feist3987c8b2019-05-13 10:43:17 -0700606 if (pathIt->second.empty())
James Feistf0096a02019-02-21 11:25:22 -0800607 {
James Feist3987c8b2019-05-13 10:43:17 -0700608 pathIt = configurations.erase(pathIt);
James Feistf0096a02019-02-21 11:25:22 -0800609 }
James Feist3987c8b2019-05-13 10:43:17 -0700610 else
James Feistf0096a02019-02-21 11:25:22 -0800611 {
James Feist3987c8b2019-05-13 10:43:17 -0700612 pathIt++;
James Feistf0096a02019-02-21 11:25:22 -0800613 }
James Feist7136a5a2018-07-19 09:52:05 -0700614 }
615 }
James Feist8c3c51e2018-08-08 16:31:43 -0700616
Josh Lehan998fbe62020-09-20 21:21:05 -0700617 // On D-Bus, although not necessary,
618 // having the "zoneID" field can still be useful,
619 // as it is used for diagnostic messages,
620 // logging file names, and so on.
621 // Accept optional "ZoneIndex" parameter to explicitly specify.
622 // If not present, or not unique, auto-assign index,
623 // using 0-based numbering, ensuring uniqueness.
624 std::map<std::string, int64_t> foundZones;
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700625 for (const auto& configuration : configurations)
James Feist7136a5a2018-07-19 09:52:05 -0700626 {
627 auto findZone =
628 configuration.second.find(pidZoneConfigurationInterface);
629 if (findZone != configuration.second.end())
630 {
Patrick Venturee2ec0f62018-09-04 12:30:27 -0700631 const auto& zone = findZone->second;
James Feistffd418b2018-11-15 14:46:36 -0800632
James Feist1f802f52019-02-08 13:51:43 -0800633 const std::string& name = std::get<std::string>(zone.at("Name"));
Josh Lehan998fbe62020-09-20 21:21:05 -0700634
635 auto findZoneIndex = zone.find("ZoneIndex");
636 if (findZoneIndex == zone.end())
637 {
638 continue;
639 }
640
641 auto ptrZoneIndex = std::get_if<double>(&(findZoneIndex->second));
642 if (!ptrZoneIndex)
643 {
644 continue;
645 }
646
647 auto desiredIndex = static_cast<int64_t>(*ptrZoneIndex);
648 auto grantedIndex = setZoneIndex(name, foundZones, desiredIndex);
649 std::cout << "Zone " << name << " is at ZoneIndex " << grantedIndex
650 << "\n";
651 }
652 }
653
654 for (const auto& configuration : configurations)
655 {
656 auto findZone =
657 configuration.second.find(pidZoneConfigurationInterface);
658 if (findZone != configuration.second.end())
659 {
660 const auto& zone = findZone->second;
661
662 const std::string& name = std::get<std::string>(zone.at("Name"));
663
664 auto index = getZoneIndex(name, foundZones);
James Feist8c3c51e2018-08-08 16:31:43 -0700665
Patrick Venturec54fbd82018-10-30 19:40:05 -0700666 auto& details = zoneDetailsConfig[index];
Josh Lehan998fbe62020-09-20 21:21:05 -0700667
James Feist3484bed2019-02-25 13:28:18 -0800668 details.minThermalOutput = std::visit(VariantToDoubleVisitor(),
669 zone.at("MinThermalOutput"));
ykchiu9fe3a3c2023-05-11 13:43:54 +0800670
671 int failsafepercent = 0;
672 auto findFailSafe = zone.find("FailSafePercent");
673 if (findFailSafe != zone.end())
674 {
675 failsafepercent = std::visit(VariantToDoubleVisitor(),
676 zone.at("FailSafePercent"));
677 }
678 details.failsafePercent = failsafepercent;
Josh Lehan9f9a06a2022-12-14 10:39:45 -0800679
Harvey Wu239aa7d2022-11-18 08:43:34 +0800680 getCycleTimeSetting(zone, index, "CycleIntervalTimeMS",
681 details.cycleTime.cycleIntervalTimeMS);
682 getCycleTimeSetting(zone, index, "UpdateThermalsTimeMS",
683 details.cycleTime.updateThermalsTimeMS);
Delphine CC Chiu97889632023-11-06 11:32:46 +0800684
685 bool accumulateSetPoint = false;
686 auto findAccSetPoint = zone.find("AccumulateSetPoint");
687 if (findAccSetPoint != zone.end())
688 {
689 accumulateSetPoint = std::get<bool>(findAccSetPoint->second);
690 }
691 details.accumulateSetPoint = accumulateSetPoint;
James Feist7136a5a2018-07-19 09:52:05 -0700692 }
693 auto findBase = configuration.second.find(pidConfigurationInterface);
Jason Lingf3b04fd2020-07-24 09:33:04 -0700694 // loop through all the PID configurations and fill out a sensor config
James Feist22c257a2018-08-31 14:07:12 -0700695 if (findBase != configuration.second.end())
James Feist7136a5a2018-07-19 09:52:05 -0700696 {
James Feist22c257a2018-08-31 14:07:12 -0700697 const auto& base =
698 configuration.second.at(pidConfigurationInterface);
ykchiu7c6d35d2023-05-10 17:01:46 +0800699 const std::string pidName =
700 sensorNameToDbusName(std::get<std::string>(base.at("Name")));
Jason Lingf3b04fd2020-07-24 09:33:04 -0700701 const std::string pidClass =
702 std::get<std::string>(base.at("Class"));
James Feist22c257a2018-08-31 14:07:12 -0700703 const std::vector<std::string>& zones =
James Feist1f802f52019-02-08 13:51:43 -0800704 std::get<std::vector<std::string>>(base.at("Zones"));
James Feist22c257a2018-08-31 14:07:12 -0700705 for (const std::string& zone : zones)
James Feist7136a5a2018-07-19 09:52:05 -0700706 {
Josh Lehan998fbe62020-09-20 21:21:05 -0700707 auto index = getZoneIndex(zone, foundZones);
708
James Feistf81f2882019-02-26 11:26:36 -0800709 conf::PIDConf& conf = zoneConfig[index];
Jason Lingf3b04fd2020-07-24 09:33:04 -0700710 std::vector<std::string> inputSensorNames(
711 std::get<std::vector<std::string>>(base.at("Inputs")));
712 std::vector<std::string> outputSensorNames;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800713 std::vector<std::string> missingAcceptableSensorNames;
Chaul Lya552fe22024-11-15 10:20:28 +0000714 std::vector<std::string> archivedInputSensorNames;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800715
716 auto findMissingAcceptable = base.find("MissingIsAcceptable");
717 if (findMissingAcceptable != base.end())
718 {
719 missingAcceptableSensorNames =
720 std::get<std::vector<std::string>>(
721 findMissingAcceptable->second);
722 }
James Feist50fdfe32018-09-24 15:51:09 -0700723
Jason Lingf3b04fd2020-07-24 09:33:04 -0700724 // assumption: all fan pids must have at least one output
725 if (pidClass == "fan")
James Feist50fdfe32018-09-24 15:51:09 -0700726 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700727 outputSensorNames = std::get<std::vector<std::string>>(
728 getPIDAttribute(base, "Outputs"));
James Feist50fdfe32018-09-24 15:51:09 -0700729 }
James Feist1738e2a2019-02-04 15:57:03 -0800730
Alex.Song8f73ad72021-10-07 00:18:27 +0800731 bool unavailableAsFailed = true;
732 auto findUnavailableAsFailed =
733 base.find("InputUnavailableAsFailed");
734 if (findUnavailableAsFailed != base.end())
735 {
736 unavailableAsFailed =
737 std::get<bool>(findUnavailableAsFailed->second);
738 }
739
Jason Lingf3b04fd2020-07-24 09:33:04 -0700740 std::vector<SensorInterfaceType> inputSensorInterfaces;
741 std::vector<SensorInterfaceType> outputSensorInterfaces;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800742 std::vector<SensorInterfaceType>
743 missingAcceptableSensorInterfaces;
744
Jason Lingf3b04fd2020-07-24 09:33:04 -0700745 /* populate an interface list for different sensor direction
746 * types (input,output)
747 */
748 /* take the Inputs from the configuration and generate
749 * a list of dbus descriptors (path, interface).
750 * Mapping can be many-to-one since an element of Inputs can be
751 * a regex
752 */
753 for (const std::string& sensorName : inputSensorNames)
James Feist50fdfe32018-09-24 15:51:09 -0700754 {
Chaul Lya552fe22024-11-15 10:20:28 +0000755#ifndef HANDLE_MISSING_OBJECT_PATHS
Jason Lingf3b04fd2020-07-24 09:33:04 -0700756 findSensors(sensors, sensorNameToDbusName(sensorName),
757 inputSensorInterfaces);
Chaul Lya552fe22024-11-15 10:20:28 +0000758#else
759 std::vector<std::pair<std::string, std::string>>
760 sensorPathIfacePairs;
761 auto found =
762 findSensors(sensors, sensorNameToDbusName(sensorName),
763 sensorPathIfacePairs);
764 if (found)
765 {
766 inputSensorInterfaces.insert(
767 inputSensorInterfaces.end(),
768 sensorPathIfacePairs.begin(),
769 sensorPathIfacePairs.end());
770 }
771 else if (pidClass != "fan")
772 {
773 if (std::find(missingAcceptableSensorNames.begin(),
774 missingAcceptableSensorNames.end(),
775 sensorName) ==
776 missingAcceptableSensorNames.end())
777 {
778 std::cerr
779 << "Pid controller: Missing a missing-unacceptable sensor from D-Bus "
780 << sensorName << "\n";
781 std::string inputSensorName =
782 sensorNameToDbusName(sensorName);
783 auto& config = sensorConfig[inputSensorName];
784 archivedInputSensorNames.push_back(inputSensorName);
785 config.type = pidClass;
786 config.readPath =
787 getSensorPath(config.type, inputSensorName);
788 config.timeout = 0;
789 config.ignoreDbusMinMax = true;
790 config.unavailableAsFailed = unavailableAsFailed;
791 }
792 else
793 {
794 // When an input sensor is NOT on DBus, and it's in
795 // the MissingIsAcceptable list. Ignore it and
796 // continue with the next input sensor.
797 std::cout
798 << "Pid controller: Missing a missing-acceptable sensor from D-Bus "
799 << sensorName << "\n";
800 continue;
801 }
802 }
803#endif
Jason Lingf3b04fd2020-07-24 09:33:04 -0700804 }
805 for (const std::string& sensorName : outputSensorNames)
806 {
807 findSensors(sensors, sensorNameToDbusName(sensorName),
808 outputSensorInterfaces);
James Feist1738e2a2019-02-04 15:57:03 -0800809 }
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800810 for (const std::string& sensorName :
811 missingAcceptableSensorNames)
812 {
813 findSensors(sensors, sensorNameToDbusName(sensorName),
814 missingAcceptableSensorInterfaces);
815 }
James Feist50fdfe32018-09-24 15:51:09 -0700816
Jason Lingf3b04fd2020-07-24 09:33:04 -0700817 for (const SensorInterfaceType& inputSensorInterface :
818 inputSensorInterfaces)
James Feist1738e2a2019-02-04 15:57:03 -0800819 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700820 const std::string& dbusInterface =
821 inputSensorInterface.second;
822 const std::string& inputSensorPath =
823 inputSensorInterface.first;
Josh Lehanfb82a872020-09-20 21:48:22 -0700824
825 // Setting timeout to 0 is intentional, as D-Bus passive
826 // sensor updates are pushed in, not pulled by timer poll.
827 // Setting ignoreDbusMinMax is intentional, as this
828 // prevents normalization of values to [0.0, 1.0] range,
829 // which would mess up the PID loop math.
830 // All non-fan PID classes should be initialized this way.
831 // As for why a fan should not use this code path, see
832 // the ed1dafdf168def37c65bfb7a5efd18d9dbe04727 commit.
Josh Lehan23e22b92022-11-12 22:37:58 -0800833 if ((pidClass == "temp") || (pidClass == "margin") ||
834 (pidClass == "power") || (pidClass == "powersum"))
James Feist50fdfe32018-09-24 15:51:09 -0700835 {
Harvey.Wued1dafd2022-02-09 13:53:20 +0800836 std::string inputSensorName =
837 getSensorNameFromPath(inputSensorPath);
838 auto& config = sensorConfig[inputSensorName];
Chaul Lya552fe22024-11-15 10:20:28 +0000839 archivedInputSensorNames.push_back(inputSensorName);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800840 config.type = pidClass;
841 config.readPath = inputSensorInterface.first;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700842 config.timeout = 0;
843 config.ignoreDbusMinMax = true;
Alex.Song8f73ad72021-10-07 00:18:27 +0800844 config.unavailableAsFailed = unavailableAsFailed;
James Feist50fdfe32018-09-24 15:51:09 -0700845 }
Josh Lehanfb82a872020-09-20 21:48:22 -0700846
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100847 if (dbusInterface != SensorValue::interface)
James Feist50fdfe32018-09-24 15:51:09 -0700848 {
Jason Lingf3b04fd2020-07-24 09:33:04 -0700849 /* all expected inputs in the configuration are expected
850 * to be sensor interfaces
851 */
Ed Tanous7d6e2252025-06-27 10:45:25 -0700852 throw std::runtime_error(std::format(
853 "sensor at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100854 inputSensorPath, dbusInterface,
855 SensorValue::interface));
James Feist50fdfe32018-09-24 15:51:09 -0700856 }
857 }
James Feist1738e2a2019-02-04 15:57:03 -0800858
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800859 // MissingIsAcceptable same postprocessing as Inputs
860 missingAcceptableSensorNames.clear();
861 for (const SensorInterfaceType&
862 missingAcceptableSensorInterface :
863 missingAcceptableSensorInterfaces)
864 {
865 const std::string& dbusInterface =
866 missingAcceptableSensorInterface.second;
867 const std::string& missingAcceptableSensorPath =
868 missingAcceptableSensorInterface.first;
869
870 std::string missingAcceptableSensorName =
871 getSensorNameFromPath(missingAcceptableSensorPath);
872 missingAcceptableSensorNames.push_back(
873 missingAcceptableSensorName);
874
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100875 if (dbusInterface != SensorValue::interface)
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800876 {
877 /* MissingIsAcceptable same error checking as Inputs
878 */
Ed Tanous7d6e2252025-06-27 10:45:25 -0700879 throw std::runtime_error(std::format(
880 "sensor at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
881 missingAcceptableSensorPath, dbusInterface,
Alexander Hansendae4a3a2025-11-11 17:12:01 +0100882 SensorValue::interface));
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800883 }
884 }
885
Jason Lingf3b04fd2020-07-24 09:33:04 -0700886 /* fan pids need to pair up tach sensors with their pwm
887 * counterparts
888 */
889 if (pidClass == "fan")
890 {
891 /* If a PID is a fan there should be either
892 * (1) one output(pwm) per input(tach)
893 * OR
894 * (2) one putput(pwm) for all inputs(tach)
895 * everything else indicates a bad configuration.
896 */
897 bool singlePwm = false;
898 if (outputSensorInterfaces.size() == 1)
899 {
900 /* one pwm, set write paths for all fan sensors to it */
901 singlePwm = true;
902 }
903 else if (inputSensorInterfaces.size() ==
904 outputSensorInterfaces.size())
905 {
906 /* one to one mapping, each fan sensor gets its own pwm
907 * control */
908 singlePwm = false;
909 }
910 else
911 {
912 throw std::runtime_error(
913 "fan PID has invalid number of Outputs");
914 }
915 std::string fanSensorName;
916 std::string pwmPath;
917 std::string pwmInterface;
Harvey.Wued1dafd2022-02-09 13:53:20 +0800918 std::string pwmSensorName;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700919 if (singlePwm)
920 {
921 /* if just a single output(pwm) is provided then use
922 * that pwm control path for all the fan sensor write
923 * path configs
924 */
925 pwmPath = outputSensorInterfaces.at(0).first;
926 pwmInterface = outputSensorInterfaces.at(0).second;
927 }
928 for (uint32_t idx = 0; idx < inputSensorInterfaces.size();
929 idx++)
930 {
931 if (!singlePwm)
932 {
933 pwmPath = outputSensorInterfaces.at(idx).first;
934 pwmInterface =
935 outputSensorInterfaces.at(idx).second;
936 }
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100937 if (ControlFanPwm::interface != pwmInterface)
Jason Lingf3b04fd2020-07-24 09:33:04 -0700938 {
Ed Tanous7d6e2252025-06-27 10:45:25 -0700939 throw std::runtime_error(std::format(
940 "fan pwm control at dbus path [{}] has an interface [{}] that does not match the expected interface of {}",
Alexander Hansenbd2c98e2025-11-12 12:47:00 +0100941 pwmPath, pwmInterface,
942 ControlFanPwm::interface));
Jason Lingf3b04fd2020-07-24 09:33:04 -0700943 }
944 const std::string& fanPath =
945 inputSensorInterfaces.at(idx).first;
946 fanSensorName = getSensorNameFromPath(fanPath);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800947 pwmSensorName = getSensorNameFromPath(pwmPath);
948 std::string fanPwmIndex = fanSensorName + pwmSensorName;
Chaul Lya552fe22024-11-15 10:20:28 +0000949 archivedInputSensorNames.push_back(fanPwmIndex);
Harvey.Wued1dafd2022-02-09 13:53:20 +0800950 auto& fanConfig = sensorConfig[fanPwmIndex];
951 fanConfig.type = pidClass;
952 fanConfig.readPath = fanPath;
Jason Lingf3b04fd2020-07-24 09:33:04 -0700953 fanConfig.writePath = pwmPath;
954 // todo: un-hardcode this if there are fans with
955 // different ranges
956 fanConfig.max = 255;
957 fanConfig.min = 0;
958 }
959 }
James Feist11d243d2019-06-24 16:18:40 -0700960 // if the sensors aren't available in the current state, don't
961 // add them to the configuration.
Chaul Lya552fe22024-11-15 10:20:28 +0000962 if (archivedInputSensorNames.empty())
James Feist11d243d2019-06-24 16:18:40 -0700963 {
964 continue;
965 }
966
James Feist5ec20272019-07-10 11:59:57 -0700967 std::string offsetType;
James Feist50fdfe32018-09-24 15:51:09 -0700968
James Feist5ec20272019-07-10 11:59:57 -0700969 // SetPointOffset is a threshold value to pull from the sensor
970 // to apply an offset. For upper thresholds this means the
971 // setpoint is usually negative.
972 auto findSetpointOffset = base.find("SetPointOffset");
973 if (findSetpointOffset != base.end())
James Feist22c257a2018-08-31 14:07:12 -0700974 {
James Feist5ec20272019-07-10 11:59:57 -0700975 offsetType =
976 std::get<std::string>(findSetpointOffset->second);
977 if (std::find(thresholds::types.begin(),
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400978 thresholds::types.end(), offsetType) ==
979 thresholds::types.end())
James Feist5ec20272019-07-10 11:59:57 -0700980 {
Patrick Williamsbd63bca2024-08-16 15:21:10 -0400981 throw std::runtime_error(
982 "Unsupported type: " + offsetType);
James Feist5ec20272019-07-10 11:59:57 -0700983 }
984 }
985
Josh Lehan31058fd2023-01-13 11:06:16 -0800986 std::vector<double> inputTempToMargin;
987
988 auto findTempToMargin = base.find("TempToMargin");
989 if (findTempToMargin != base.end())
990 {
991 inputTempToMargin =
992 std::get<std::vector<double>>(findTempToMargin->second);
993 }
994
995 std::vector<pid_control::conf::SensorInput> sensorInputs =
Chaul Lya552fe22024-11-15 10:20:28 +0000996 spliceInputs(archivedInputSensorNames, inputTempToMargin,
Josh Lehan3f0f7bc2023-02-13 01:45:29 -0800997 missingAcceptableSensorNames);
Josh Lehan31058fd2023-01-13 11:06:16 -0800998
James Feist5ec20272019-07-10 11:59:57 -0700999 if (offsetType.empty())
1000 {
ykchiu7c6d35d2023-05-10 17:01:46 +08001001 conf::ControllerInfo& info = conf[pidName];
Josh Lehan31058fd2023-01-13 11:06:16 -08001002 info.inputs = std::move(sensorInputs);
Patrick Venture73823182020-10-08 15:12:51 -07001003 populatePidInfo(bus, base, info, nullptr, sensorConfig);
James Feist22c257a2018-08-31 14:07:12 -07001004 }
1005 else
1006 {
James Feist5ec20272019-07-10 11:59:57 -07001007 // we have to split up the inputs, as in practice t-control
1008 // values will differ, making setpoints differ
Josh Lehan31058fd2023-01-13 11:06:16 -08001009 for (const pid_control::conf::SensorInput& input :
1010 sensorInputs)
James Feist5ec20272019-07-10 11:59:57 -07001011 {
Josh Lehan31058fd2023-01-13 11:06:16 -08001012 conf::ControllerInfo& info = conf[input.name];
James Feist5ec20272019-07-10 11:59:57 -07001013 info.inputs.emplace_back(input);
Patrick Venture73823182020-10-08 15:12:51 -07001014 populatePidInfo(bus, base, info, &offsetType,
1015 sensorConfig);
James Feist5ec20272019-07-10 11:59:57 -07001016 }
James Feist22c257a2018-08-31 14:07:12 -07001017 }
James Feist22c257a2018-08-31 14:07:12 -07001018 }
1019 }
1020 auto findStepwise =
1021 configuration.second.find(stepwiseConfigurationInterface);
1022 if (findStepwise != configuration.second.end())
1023 {
1024 const auto& base = findStepwise->second;
ykchiu7c6d35d2023-05-10 17:01:46 +08001025 const std::string pidName =
1026 sensorNameToDbusName(std::get<std::string>(base.at("Name")));
James Feist22c257a2018-08-31 14:07:12 -07001027 const std::vector<std::string>& zones =
James Feist1f802f52019-02-08 13:51:43 -08001028 std::get<std::vector<std::string>>(base.at("Zones"));
James Feist22c257a2018-08-31 14:07:12 -07001029 for (const std::string& zone : zones)
1030 {
Josh Lehan998fbe62020-09-20 21:21:05 -07001031 auto index = getZoneIndex(zone, foundZones);
1032
James Feistf81f2882019-02-26 11:26:36 -08001033 conf::PIDConf& conf = zoneConfig[index];
James Feist50fdfe32018-09-24 15:51:09 -07001034
1035 std::vector<std::string> inputs;
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001036 std::vector<std::string> missingAcceptableSensors;
1037 std::vector<std::string> missingAcceptableSensorNames;
James Feist50fdfe32018-09-24 15:51:09 -07001038 std::vector<std::string> sensorNames =
James Feist1f802f52019-02-08 13:51:43 -08001039 std::get<std::vector<std::string>>(base.at("Inputs"));
James Feist50fdfe32018-09-24 15:51:09 -07001040
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001041 auto findMissingAcceptable = base.find("MissingIsAcceptable");
1042 if (findMissingAcceptable != base.end())
1043 {
1044 missingAcceptableSensorNames =
1045 std::get<std::vector<std::string>>(
1046 findMissingAcceptable->second);
1047 }
1048
Alex.Song8f73ad72021-10-07 00:18:27 +08001049 bool unavailableAsFailed = true;
1050 auto findUnavailableAsFailed =
1051 base.find("InputUnavailableAsFailed");
1052 if (findUnavailableAsFailed != base.end())
1053 {
1054 unavailableAsFailed =
1055 std::get<bool>(findUnavailableAsFailed->second);
1056 }
1057
James Feist1738e2a2019-02-04 15:57:03 -08001058 bool sensorFound = false;
James Feist50fdfe32018-09-24 15:51:09 -07001059 for (const std::string& sensorName : sensorNames)
1060 {
James Feist1738e2a2019-02-04 15:57:03 -08001061 std::vector<std::pair<std::string, std::string>>
1062 sensorPathIfacePairs;
Jason Lingf3b04fd2020-07-24 09:33:04 -07001063 if (!findSensors(sensors, sensorNameToDbusName(sensorName),
1064 sensorPathIfacePairs))
James Feist50fdfe32018-09-24 15:51:09 -07001065 {
Chaul Lya552fe22024-11-15 10:20:28 +00001066#ifndef HANDLE_MISSING_OBJECT_PATHS
James Feist50fdfe32018-09-24 15:51:09 -07001067 break;
Chaul Lya552fe22024-11-15 10:20:28 +00001068#else
1069 if (std::find(missingAcceptableSensorNames.begin(),
1070 missingAcceptableSensorNames.end(),
1071 sensorName) ==
1072 missingAcceptableSensorNames.end())
1073 {
1074 // When an input sensor is NOT on DBus, and it's NOT
1075 // in the MissingIsAcceptable list. Build it as a
1076 // failed sensor with default information (temp
1077 // sensor path, temp type, ...)
1078 std::cerr
1079 << "Stepwise controller: Missing a missing-unacceptable sensor from D-Bus "
1080 << sensorName << "\n";
1081 std::string shortName =
1082 sensorNameToDbusName(sensorName);
1083
1084 inputs.push_back(shortName);
1085 auto& config = sensorConfig[shortName];
1086 config.type = "temp";
1087 config.readPath =
1088 getSensorPath(config.type, shortName);
1089 config.ignoreDbusMinMax = true;
1090 config.unavailableAsFailed = unavailableAsFailed;
1091 // todo: maybe un-hardcode this if we run into
1092 // slower timeouts with sensors
1093
1094 config.timeout = 0;
1095 sensorFound = true;
1096 }
1097 else
1098 {
1099 // When an input sensor is NOT on DBus, and it's in
1100 // the MissingIsAcceptable list. Ignore it and
1101 // continue with the next input sensor.
1102 std::cout
1103 << "Stepwise controller: Missing a missing-acceptable sensor from D-Bus "
1104 << sensorName << "\n";
1105 continue;
1106 }
1107#endif
James Feist50fdfe32018-09-24 15:51:09 -07001108 }
Chaul Lya552fe22024-11-15 10:20:28 +00001109 else
James Feist1738e2a2019-02-04 15:57:03 -08001110 {
Chaul Lya552fe22024-11-15 10:20:28 +00001111 for (const auto& sensorPathIfacePair :
1112 sensorPathIfacePairs)
1113 {
1114 std::string shortName = getSensorNameFromPath(
1115 sensorPathIfacePair.first);
James Feist50fdfe32018-09-24 15:51:09 -07001116
Chaul Lya552fe22024-11-15 10:20:28 +00001117 inputs.push_back(shortName);
1118 auto& config = sensorConfig[shortName];
1119 config.readPath = sensorPathIfacePair.first;
1120 config.type = "temp";
1121 config.ignoreDbusMinMax = true;
1122 config.unavailableAsFailed = unavailableAsFailed;
1123 // todo: maybe un-hardcode this if we run into
1124 // slower timeouts with sensors
James Feist1738e2a2019-02-04 15:57:03 -08001125
Chaul Lya552fe22024-11-15 10:20:28 +00001126 config.timeout = 0;
1127 sensorFound = true;
1128 }
James Feist1738e2a2019-02-04 15:57:03 -08001129 }
James Feist50fdfe32018-09-24 15:51:09 -07001130 }
1131 if (!sensorFound)
1132 {
1133 continue;
1134 }
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001135
1136 // MissingIsAcceptable same postprocessing as Inputs
1137 for (const std::string& missingAcceptableSensorName :
1138 missingAcceptableSensorNames)
1139 {
1140 std::vector<std::pair<std::string, std::string>>
1141 sensorPathIfacePairs;
1142 if (!findSensors(
1143 sensors,
1144 sensorNameToDbusName(missingAcceptableSensorName),
1145 sensorPathIfacePairs))
1146 {
Chaul Lya552fe22024-11-15 10:20:28 +00001147#ifndef HANDLE_MISSING_OBJECT_PATHS
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001148 break;
Chaul Lya552fe22024-11-15 10:20:28 +00001149#else
1150 // When a sensor in the MissingIsAcceptable list is NOT
1151 // on DBus and it still reaches here, which contradicts
1152 // to what we did in the Input sensor building step.
1153 // Continue.
1154 continue;
1155#endif
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001156 }
1157
1158 for (const auto& sensorPathIfacePair : sensorPathIfacePairs)
1159 {
1160 std::string shortName =
1161 getSensorNameFromPath(sensorPathIfacePair.first);
1162
1163 missingAcceptableSensors.push_back(shortName);
1164 }
1165 }
1166
ykchiu7c6d35d2023-05-10 17:01:46 +08001167 conf::ControllerInfo& info = conf[pidName];
Josh Lehan31058fd2023-01-13 11:06:16 -08001168
1169 std::vector<double> inputTempToMargin;
1170
1171 auto findTempToMargin = base.find("TempToMargin");
1172 if (findTempToMargin != base.end())
1173 {
1174 inputTempToMargin =
1175 std::get<std::vector<double>>(findTempToMargin->second);
1176 }
1177
Josh Lehan3f0f7bc2023-02-13 01:45:29 -08001178 info.inputs = spliceInputs(inputs, inputTempToMargin,
1179 missingAcceptableSensors);
James Feist50fdfe32018-09-24 15:51:09 -07001180
James Feist22c257a2018-08-31 14:07:12 -07001181 info.type = "stepwise";
1182 info.stepwiseInfo.ts = 1.0; // currently unused
James Feist3dfaafd2018-09-20 15:46:58 -07001183 info.stepwiseInfo.positiveHysteresis = 0.0;
1184 info.stepwiseInfo.negativeHysteresis = 0.0;
James Feist608304d2019-02-25 10:01:42 -08001185 std::string subtype = std::get<std::string>(base.at("Class"));
1186
1187 info.stepwiseInfo.isCeiling = (subtype == "Ceiling");
James Feist3dfaafd2018-09-20 15:46:58 -07001188 auto findPosHyst = base.find("PositiveHysteresis");
1189 auto findNegHyst = base.find("NegativeHysteresis");
1190 if (findPosHyst != base.end())
1191 {
James Feist1f802f52019-02-08 13:51:43 -08001192 info.stepwiseInfo.positiveHysteresis = std::visit(
James Feist208abce2018-12-06 09:59:10 -08001193 VariantToDoubleVisitor(), findPosHyst->second);
James Feist3dfaafd2018-09-20 15:46:58 -07001194 }
1195 if (findNegHyst != base.end())
1196 {
James Feist5782ab82019-04-02 08:38:48 -07001197 info.stepwiseInfo.negativeHysteresis = std::visit(
James Feist208abce2018-12-06 09:59:10 -08001198 VariantToDoubleVisitor(), findNegHyst->second);
James Feist3dfaafd2018-09-20 15:46:58 -07001199 }
James Feist22c257a2018-08-31 14:07:12 -07001200 std::vector<double> readings =
James Feist1f802f52019-02-08 13:51:43 -08001201 std::get<std::vector<double>>(base.at("Reading"));
James Feist22c257a2018-08-31 14:07:12 -07001202 if (readings.size() > ec::maxStepwisePoints)
1203 {
1204 throw std::invalid_argument("Too many stepwise points.");
1205 }
1206 if (readings.empty())
1207 {
1208 throw std::invalid_argument(
1209 "Must have one stepwise point.");
1210 }
1211 std::copy(readings.begin(), readings.end(),
1212 info.stepwiseInfo.reading);
1213 if (readings.size() < ec::maxStepwisePoints)
1214 {
1215 info.stepwiseInfo.reading[readings.size()] =
Patrick Venture5f59c0f2018-11-11 12:55:14 -08001216 std::numeric_limits<double>::quiet_NaN();
James Feist22c257a2018-08-31 14:07:12 -07001217 }
1218 std::vector<double> outputs =
James Feist1f802f52019-02-08 13:51:43 -08001219 std::get<std::vector<double>>(base.at("Output"));
James Feist22c257a2018-08-31 14:07:12 -07001220 if (readings.size() != outputs.size())
1221 {
1222 throw std::invalid_argument(
1223 "Outputs size must match readings");
1224 }
1225 std::copy(outputs.begin(), outputs.end(),
1226 info.stepwiseInfo.output);
1227 if (outputs.size() < ec::maxStepwisePoints)
1228 {
1229 info.stepwiseInfo.output[outputs.size()] =
Patrick Venture5f59c0f2018-11-11 12:55:14 -08001230 std::numeric_limits<double>::quiet_NaN();
James Feist22c257a2018-08-31 14:07:12 -07001231 }
James Feist7136a5a2018-07-19 09:52:05 -07001232 }
1233 }
1234 }
Patrick Venture39199b42020-10-08 14:40:29 -07001235 if constexpr (pid_control::conf::DEBUG)
James Feist7136a5a2018-07-19 09:52:05 -07001236 {
Patrick Venture39199b42020-10-08 14:40:29 -07001237 debugPrint(sensorConfig, zoneConfig, zoneDetailsConfig);
James Feist7136a5a2018-07-19 09:52:05 -07001238 }
James Feistc959c422018-11-01 12:33:40 -07001239 if (zoneConfig.empty() || zoneDetailsConfig.empty())
James Feist50fdfe32018-09-24 15:51:09 -07001240 {
James Feist1fe08952019-05-07 09:17:16 -07001241 std::cerr
1242 << "No fan zones, application pausing until new configuration\n";
1243 return false;
James Feist50fdfe32018-09-24 15:51:09 -07001244 }
James Feist1fe08952019-05-07 09:17:16 -07001245 return true;
James Feist7136a5a2018-07-19 09:52:05 -07001246}
Patrick Venturea0764872020-08-08 07:48:43 -07001247
James Feist7136a5a2018-07-19 09:52:05 -07001248} // namespace dbus_configuration
Patrick Venturea0764872020-08-08 07:48:43 -07001249} // namespace pid_control