1 /* 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 */ 16 17 #include "conf.hpp" 18 #include "util.hpp" 19 20 #include <algorithm> 21 #include <boost/asio/steady_timer.hpp> 22 #include <chrono> 23 #include <functional> 24 #include <iostream> 25 #include <list> 26 #include <regex> 27 #include <sdbusplus/bus.hpp> 28 #include <sdbusplus/bus/match.hpp> 29 #include <sdbusplus/exception.hpp> 30 #include <set> 31 #include <unordered_map> 32 #include <variant> 33 34 static constexpr bool DEBUG = false; // enable to print found configuration 35 36 extern std::map<std::string, struct conf::SensorConfig> sensorConfig; 37 extern std::map<int64_t, conf::PIDConf> zoneConfig; 38 extern std::map<int64_t, struct conf::ZoneConfig> zoneDetailsConfig; 39 40 constexpr const char* pidConfigurationInterface = 41 "xyz.openbmc_project.Configuration.Pid"; 42 constexpr const char* objectManagerInterface = 43 "org.freedesktop.DBus.ObjectManager"; 44 constexpr const char* pidZoneConfigurationInterface = 45 "xyz.openbmc_project.Configuration.Pid.Zone"; 46 constexpr const char* stepwiseConfigurationInterface = 47 "xyz.openbmc_project.Configuration.Stepwise"; 48 constexpr const char* thermalControlIface = 49 "xyz.openbmc_project.Control.ThermalMode"; 50 constexpr const char* sensorInterface = "xyz.openbmc_project.Sensor.Value"; 51 constexpr const char* pwmInterface = "xyz.openbmc_project.Control.FanPwm"; 52 53 using Association = std::tuple<std::string, std::string, std::string>; 54 using Associations = std::vector<Association>; 55 56 namespace thresholds 57 { 58 constexpr const char* warningInterface = 59 "xyz.openbmc_project.Sensor.Threshold.Warning"; 60 constexpr const char* criticalInterface = 61 "xyz.openbmc_project.Sensor.Threshold.Critical"; 62 const std::array<const char*, 4> types = {"CriticalLow", "CriticalHigh", 63 "WarningLow", "WarningHigh"}; 64 65 } // namespace thresholds 66 67 namespace dbus_configuration 68 { 69 70 using DbusVariantType = 71 std::variant<uint64_t, int64_t, double, std::string, 72 std::vector<std::string>, std::vector<double>>; 73 74 bool findSensors(const std::unordered_map<std::string, std::string>& sensors, 75 const std::string& search, 76 std::vector<std::pair<std::string, std::string>>& matches) 77 { 78 std::smatch match; 79 std::regex reg(search + '$'); 80 for (const auto& sensor : sensors) 81 { 82 if (std::regex_search(sensor.first, match, reg)) 83 { 84 matches.push_back(sensor); 85 } 86 } 87 return matches.size() > 0; 88 } 89 90 // this function prints the configuration into a form similar to the cpp 91 // generated code to help in verification, should be turned off during normal 92 // use 93 void debugPrint(void) 94 { 95 // print sensor config 96 std::cout << "sensor config:\n"; 97 std::cout << "{\n"; 98 for (const auto& pair : sensorConfig) 99 { 100 101 std::cout << "\t{" << pair.first << ",\n\t\t{"; 102 std::cout << pair.second.type << ", "; 103 std::cout << pair.second.readPath << ", "; 104 std::cout << pair.second.writePath << ", "; 105 std::cout << pair.second.min << ", "; 106 std::cout << pair.second.max << ", "; 107 std::cout << pair.second.timeout << "},\n\t},\n"; 108 } 109 std::cout << "}\n\n"; 110 std::cout << "ZoneDetailsConfig\n"; 111 std::cout << "{\n"; 112 for (const auto& zone : zoneDetailsConfig) 113 { 114 std::cout << "\t{" << zone.first << ",\n"; 115 std::cout << "\t\t{" << zone.second.minThermalOutput << ", "; 116 std::cout << zone.second.failsafePercent << "}\n\t},\n"; 117 } 118 std::cout << "}\n\n"; 119 std::cout << "ZoneConfig\n"; 120 std::cout << "{\n"; 121 for (const auto& zone : zoneConfig) 122 { 123 std::cout << "\t{" << zone.first << "\n"; 124 for (const auto& pidconf : zone.second) 125 { 126 std::cout << "\t\t{" << pidconf.first << ",\n"; 127 std::cout << "\t\t\t{" << pidconf.second.type << ",\n"; 128 std::cout << "\t\t\t{"; 129 for (const auto& input : pidconf.second.inputs) 130 { 131 std::cout << "\n\t\t\t" << input << ",\n"; 132 } 133 std::cout << "\t\t\t}\n"; 134 std::cout << "\t\t\t" << pidconf.second.setpoint << ",\n"; 135 std::cout << "\t\t\t{" << pidconf.second.pidInfo.ts << ",\n"; 136 std::cout << "\t\t\t" << pidconf.second.pidInfo.proportionalCoeff 137 << ",\n"; 138 std::cout << "\t\t\t" << pidconf.second.pidInfo.integralCoeff 139 << ",\n"; 140 std::cout << "\t\t\t" << pidconf.second.pidInfo.feedFwdOffset 141 << ",\n"; 142 std::cout << "\t\t\t" << pidconf.second.pidInfo.feedFwdGain 143 << ",\n"; 144 std::cout << "\t\t\t{" << pidconf.second.pidInfo.integralLimit.min 145 << "," << pidconf.second.pidInfo.integralLimit.max 146 << "},\n"; 147 std::cout << "\t\t\t{" << pidconf.second.pidInfo.outLim.min << "," 148 << pidconf.second.pidInfo.outLim.max << "},\n"; 149 std::cout << "\t\t\t" << pidconf.second.pidInfo.slewNeg << ",\n"; 150 std::cout << "\t\t\t" << pidconf.second.pidInfo.slewPos << ",\n"; 151 std::cout << "\t\t\t}\n\t\t}\n"; 152 } 153 std::cout << "\t},\n"; 154 } 155 std::cout << "}\n\n"; 156 } 157 158 size_t getZoneIndex(const std::string& name, std::vector<std::string>& zones) 159 { 160 auto it = std::find(zones.begin(), zones.end(), name); 161 if (it == zones.end()) 162 { 163 zones.emplace_back(name); 164 it = zones.end() - 1; 165 } 166 167 return it - zones.begin(); 168 } 169 170 std::vector<std::string> getSelectedProfiles(sdbusplus::bus::bus& bus) 171 { 172 std::vector<std::string> ret; 173 auto mapper = 174 bus.new_method_call("xyz.openbmc_project.ObjectMapper", 175 "/xyz/openbmc_project/object_mapper", 176 "xyz.openbmc_project.ObjectMapper", "GetSubTree"); 177 mapper.append("/", 0, std::array<const char*, 1>{thermalControlIface}); 178 std::unordered_map< 179 std::string, std::unordered_map<std::string, std::vector<std::string>>> 180 respData; 181 182 try 183 { 184 auto resp = bus.call(mapper); 185 resp.read(respData); 186 } 187 catch (sdbusplus::exception_t&) 188 { 189 // can't do anything without mapper call data 190 throw std::runtime_error("ObjectMapper Call Failure"); 191 } 192 if (respData.empty()) 193 { 194 // if the user has profiles but doesn't expose the interface to select 195 // one, just go ahead without using profiles 196 return ret; 197 } 198 199 // assumption is that we should only have a small handful of selected 200 // profiles at a time (probably only 1), so calling each individually should 201 // not incur a large cost 202 for (const auto& objectPair : respData) 203 { 204 const std::string& path = objectPair.first; 205 for (const auto& ownerPair : objectPair.second) 206 { 207 const std::string& busName = ownerPair.first; 208 auto getProfile = 209 bus.new_method_call(busName.c_str(), path.c_str(), 210 "org.freedesktop.DBus.Properties", "Get"); 211 getProfile.append(thermalControlIface, "Current"); 212 std::variant<std::string> variantResp; 213 try 214 { 215 auto resp = bus.call(getProfile); 216 resp.read(variantResp); 217 } 218 catch (sdbusplus::exception_t&) 219 { 220 throw std::runtime_error("Failure getting profile"); 221 } 222 std::string mode = std::get<std::string>(variantResp); 223 ret.emplace_back(std::move(mode)); 224 } 225 } 226 if constexpr (DEBUG) 227 { 228 std::cout << "Profiles selected: "; 229 for (const auto& profile : ret) 230 { 231 std::cout << profile << " "; 232 } 233 std::cout << "\n"; 234 } 235 return ret; 236 } 237 238 int eventHandler(sd_bus_message* m, void* context, sd_bus_error*) 239 { 240 241 if (context == nullptr || m == nullptr) 242 { 243 throw std::runtime_error("Invalid match"); 244 } 245 246 // we skip associations because the mapper populates these, not the sensors 247 const std::array<const char*, 1> skipList = { 248 "xyz.openbmc_project.Association"}; 249 250 sdbusplus::message::message message(m); 251 if (std::string(message.get_member()) == "InterfacesAdded") 252 { 253 sdbusplus::message::object_path path; 254 std::unordered_map< 255 std::string, 256 std::unordered_map<std::string, std::variant<Associations, bool>>> 257 data; 258 259 message.read(path, data); 260 261 for (const char* skip : skipList) 262 { 263 auto find = data.find(skip); 264 if (find != data.end()) 265 { 266 data.erase(find); 267 if (data.empty()) 268 { 269 return 1; 270 } 271 } 272 } 273 } 274 275 boost::asio::steady_timer* timer = 276 static_cast<boost::asio::steady_timer*>(context); 277 278 // do a brief sleep as we tend to get a bunch of these events at 279 // once 280 timer->expires_after(std::chrono::seconds(2)); 281 timer->async_wait([](const boost::system::error_code ec) { 282 if (ec == boost::asio::error::operation_aborted) 283 { 284 /* another timer started*/ 285 return; 286 } 287 288 std::cout << "New configuration detected, reloading\n."; 289 tryRestartControlLoops(); 290 }); 291 292 return 1; 293 } 294 295 void createMatches(sdbusplus::bus::bus& bus, boost::asio::steady_timer& timer) 296 { 297 // this is a list because the matches can't be moved 298 static std::list<sdbusplus::bus::match::match> matches; 299 300 const std::array<std::string, 4> interfaces = { 301 thermalControlIface, pidConfigurationInterface, 302 pidZoneConfigurationInterface, stepwiseConfigurationInterface}; 303 304 // this list only needs to be created once 305 if (!matches.empty()) 306 { 307 return; 308 } 309 310 // we restart when the configuration changes or there are new sensors 311 for (const auto& interface : interfaces) 312 { 313 matches.emplace_back( 314 bus, 315 "type='signal',member='PropertiesChanged',arg0namespace='" + 316 interface + "'", 317 eventHandler, &timer); 318 } 319 matches.emplace_back( 320 bus, 321 "type='signal',member='InterfacesAdded',arg0path='/xyz/openbmc_project/" 322 "sensors/'", 323 eventHandler, &timer); 324 } 325 326 /** 327 * retrieve an attribute from the pid configuration map 328 * @param[in] base - the PID configuration map, keys are the attributes and 329 * value is the variant associated with that attribute. 330 * @param attributeName - the name of the attribute 331 * @return a variant holding the value associated with a key 332 * @throw runtime_error : attributeName is not in base 333 */ 334 inline DbusVariantType getPIDAttribute( 335 const std::unordered_map<std::string, DbusVariantType>& base, 336 const std::string& attributeName) 337 { 338 auto search = base.find(attributeName); 339 if (search == base.end()) 340 { 341 throw std::runtime_error("missing attribute " + attributeName); 342 } 343 return search->second; 344 } 345 346 void populatePidInfo( 347 sdbusplus::bus::bus& bus, 348 const std::unordered_map<std::string, DbusVariantType>& base, 349 struct conf::ControllerInfo& info, const std::string* thresholdProperty) 350 { 351 info.type = std::get<std::string>(getPIDAttribute(base, "Class")); 352 if (info.type == "fan") 353 { 354 info.setpoint = 0; 355 } 356 else 357 { 358 info.setpoint = std::visit(VariantToDoubleVisitor(), 359 getPIDAttribute(base, "SetPoint")); 360 } 361 362 if (thresholdProperty != nullptr) 363 { 364 std::string interface; 365 if (*thresholdProperty == "WarningHigh" || 366 *thresholdProperty == "WarningLow") 367 { 368 interface = thresholds::warningInterface; 369 } 370 else 371 { 372 interface = thresholds::criticalInterface; 373 } 374 const std::string& path = sensorConfig[info.inputs.front()].readPath; 375 376 DbusHelper helper; 377 std::string service = helper.getService(bus, interface, path); 378 double reading = 0; 379 try 380 { 381 helper.getProperty(bus, service, path, interface, 382 *thresholdProperty, reading); 383 } 384 catch (const sdbusplus::exception::SdBusError& ex) 385 { 386 // unsupported threshold, leaving reading at 0 387 } 388 389 info.setpoint += reading; 390 } 391 392 info.pidInfo.ts = 1.0; // currently unused 393 info.pidInfo.proportionalCoeff = std::visit( 394 VariantToDoubleVisitor(), getPIDAttribute(base, "PCoefficient")); 395 info.pidInfo.integralCoeff = std::visit( 396 VariantToDoubleVisitor(), getPIDAttribute(base, "ICoefficient")); 397 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")); 409 info.pidInfo.slewNeg = 410 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewNeg")); 411 info.pidInfo.slewPos = 412 std::visit(VariantToDoubleVisitor(), getPIDAttribute(base, "SlewPos")); 413 double negativeHysteresis = 0; 414 double positiveHysteresis = 0; 415 416 auto findNeg = base.find("NegativeHysteresis"); 417 auto findPos = base.find("PositiveHysteresis"); 418 419 if (findNeg != base.end()) 420 { 421 negativeHysteresis = 422 std::visit(VariantToDoubleVisitor(), findNeg->second); 423 } 424 425 if (findPos != base.end()) 426 { 427 positiveHysteresis = 428 std::visit(VariantToDoubleVisitor(), findPos->second); 429 } 430 info.pidInfo.negativeHysteresis = negativeHysteresis; 431 info.pidInfo.positiveHysteresis = positiveHysteresis; 432 } 433 434 bool init(sdbusplus::bus::bus& bus, boost::asio::steady_timer& timer) 435 { 436 437 sensorConfig.clear(); 438 zoneConfig.clear(); 439 zoneDetailsConfig.clear(); 440 441 createMatches(bus, timer); 442 443 using DbusVariantType = 444 std::variant<uint64_t, int64_t, double, std::string, 445 std::vector<std::string>, std::vector<double>>; 446 447 using ManagedObjectType = std::unordered_map< 448 sdbusplus::message::object_path, 449 std::unordered_map<std::string, 450 std::unordered_map<std::string, DbusVariantType>>>; 451 452 auto mapper = 453 bus.new_method_call("xyz.openbmc_project.ObjectMapper", 454 "/xyz/openbmc_project/object_mapper", 455 "xyz.openbmc_project.ObjectMapper", "GetSubTree"); 456 mapper.append("/", 0, 457 std::array<const char*, 6>{objectManagerInterface, 458 pidConfigurationInterface, 459 pidZoneConfigurationInterface, 460 stepwiseConfigurationInterface, 461 sensorInterface, pwmInterface}); 462 std::unordered_map< 463 std::string, std::unordered_map<std::string, std::vector<std::string>>> 464 respData; 465 try 466 { 467 auto resp = bus.call(mapper); 468 resp.read(respData); 469 } 470 catch (sdbusplus::exception_t&) 471 { 472 // can't do anything without mapper call data 473 throw std::runtime_error("ObjectMapper Call Failure"); 474 } 475 476 if (respData.empty()) 477 { 478 // can't do anything without mapper call data 479 throw std::runtime_error("No configuration data available from Mapper"); 480 } 481 // create a map of pair of <has pid configuration, ObjectManager path> 482 std::unordered_map<std::string, std::pair<bool, std::string>> owners; 483 // and a map of <path, interface> for sensors 484 std::unordered_map<std::string, std::string> sensors; 485 for (const auto& objectPair : respData) 486 { 487 for (const auto& ownerPair : objectPair.second) 488 { 489 auto& owner = owners[ownerPair.first]; 490 for (const std::string& interface : ownerPair.second) 491 { 492 493 if (interface == objectManagerInterface) 494 { 495 owner.second = objectPair.first; 496 } 497 if (interface == pidConfigurationInterface || 498 interface == pidZoneConfigurationInterface || 499 interface == stepwiseConfigurationInterface) 500 { 501 owner.first = true; 502 } 503 if (interface == sensorInterface || interface == pwmInterface) 504 { 505 // we're not interested in pwm sensors, just pwm control 506 if (interface == sensorInterface && 507 objectPair.first.find("pwm") != std::string::npos) 508 { 509 continue; 510 } 511 sensors[objectPair.first] = interface; 512 } 513 } 514 } 515 } 516 ManagedObjectType configurations; 517 for (const auto& owner : owners) 518 { 519 // skip if no pid configuration (means probably a sensor) 520 if (!owner.second.first) 521 { 522 continue; 523 } 524 auto endpoint = bus.new_method_call( 525 owner.first.c_str(), owner.second.second.c_str(), 526 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); 527 ManagedObjectType configuration; 528 try 529 { 530 auto responce = bus.call(endpoint); 531 responce.read(configuration); 532 } 533 catch (sdbusplus::exception_t&) 534 { 535 // this shouldn't happen, probably means daemon crashed 536 throw std::runtime_error("Error getting managed objects from " + 537 owner.first); 538 } 539 540 for (auto& pathPair : configuration) 541 { 542 if (pathPair.second.find(pidConfigurationInterface) != 543 pathPair.second.end() || 544 pathPair.second.find(pidZoneConfigurationInterface) != 545 pathPair.second.end() || 546 pathPair.second.find(stepwiseConfigurationInterface) != 547 pathPair.second.end()) 548 { 549 configurations.emplace(pathPair); 550 } 551 } 552 } 553 554 // remove controllers from config that aren't in the current profile(s) 555 std::vector<std::string> selectedProfiles = getSelectedProfiles(bus); 556 if (selectedProfiles.size()) 557 { 558 for (auto pathIt = configurations.begin(); 559 pathIt != configurations.end();) 560 { 561 for (auto confIt = pathIt->second.begin(); 562 confIt != pathIt->second.end();) 563 { 564 auto profilesFind = confIt->second.find("Profiles"); 565 if (profilesFind == confIt->second.end()) 566 { 567 confIt++; 568 continue; // if no profiles selected, apply always 569 } 570 auto profiles = 571 std::get<std::vector<std::string>>(profilesFind->second); 572 if (profiles.empty()) 573 { 574 confIt++; 575 continue; 576 } 577 578 bool found = false; 579 for (const std::string& profile : profiles) 580 { 581 if (std::find(selectedProfiles.begin(), 582 selectedProfiles.end(), 583 profile) != selectedProfiles.end()) 584 { 585 found = true; 586 break; 587 } 588 } 589 if (found) 590 { 591 confIt++; 592 } 593 else 594 { 595 confIt = pathIt->second.erase(confIt); 596 } 597 } 598 if (pathIt->second.empty()) 599 { 600 pathIt = configurations.erase(pathIt); 601 } 602 else 603 { 604 pathIt++; 605 } 606 } 607 } 608 609 // on dbus having an index field is a bit strange, so randomly 610 // assign index based on name property 611 std::vector<std::string> foundZones; 612 for (const auto& configuration : configurations) 613 { 614 auto findZone = 615 configuration.second.find(pidZoneConfigurationInterface); 616 if (findZone != configuration.second.end()) 617 { 618 const auto& zone = findZone->second; 619 620 const std::string& name = std::get<std::string>(zone.at("Name")); 621 size_t index = getZoneIndex(name, foundZones); 622 623 auto& details = zoneDetailsConfig[index]; 624 details.minThermalOutput = std::visit(VariantToDoubleVisitor(), 625 zone.at("MinThermalOutput")); 626 details.failsafePercent = std::visit(VariantToDoubleVisitor(), 627 zone.at("FailSafePercent")); 628 } 629 auto findBase = configuration.second.find(pidConfigurationInterface); 630 if (findBase != configuration.second.end()) 631 { 632 633 const auto& base = 634 configuration.second.at(pidConfigurationInterface); 635 const std::vector<std::string>& zones = 636 std::get<std::vector<std::string>>(base.at("Zones")); 637 for (const std::string& zone : zones) 638 { 639 size_t index = getZoneIndex(zone, foundZones); 640 conf::PIDConf& conf = zoneConfig[index]; 641 642 std::vector<std::string> sensorNames = 643 std::get<std::vector<std::string>>(base.at("Inputs")); 644 auto findOutputs = 645 base.find("Outputs"); // currently only fans have outputs 646 if (findOutputs != base.end()) 647 { 648 std::vector<std::string> outputs = 649 std::get<std::vector<std::string>>(findOutputs->second); 650 sensorNames.insert(sensorNames.end(), outputs.begin(), 651 outputs.end()); 652 } 653 654 std::vector<std::string> inputs; 655 std::vector<std::pair<std::string, std::string>> 656 sensorInterfaces; 657 for (const std::string& sensorName : sensorNames) 658 { 659 std::string name = sensorName; 660 // replace spaces with underscores to be legal on dbus 661 std::replace(name.begin(), name.end(), ' ', '_'); 662 findSensors(sensors, name, sensorInterfaces); 663 } 664 665 for (const auto& sensorPathIfacePair : sensorInterfaces) 666 { 667 668 if (sensorPathIfacePair.second == sensorInterface) 669 { 670 size_t idx = 671 sensorPathIfacePair.first.find_last_of("/") + 1; 672 std::string shortName = 673 sensorPathIfacePair.first.substr(idx); 674 675 inputs.push_back(shortName); 676 auto& config = sensorConfig[shortName]; 677 config.type = std::get<std::string>(base.at("Class")); 678 config.readPath = sensorPathIfacePair.first; 679 // todo: maybe un-hardcode this if we run into slower 680 // timeouts with sensors 681 if (config.type == "temp") 682 { 683 config.timeout = 0; 684 config.ignoreDbusMinMax = true; 685 } 686 } 687 else if (sensorPathIfacePair.second == pwmInterface) 688 { 689 // copy so we can modify it 690 for (std::string otherSensor : sensorNames) 691 { 692 std::replace(otherSensor.begin(), otherSensor.end(), 693 ' ', '_'); 694 if (sensorPathIfacePair.first.find(otherSensor) != 695 std::string::npos) 696 { 697 continue; 698 } 699 700 auto& config = sensorConfig[otherSensor]; 701 config.writePath = sensorPathIfacePair.first; 702 // todo: un-hardcode this if there are fans with 703 // different ranges 704 config.max = 255; 705 config.min = 0; 706 } 707 } 708 } 709 710 // if the sensors aren't available in the current state, don't 711 // add them to the configuration. 712 if (inputs.empty()) 713 { 714 continue; 715 } 716 717 std::string offsetType; 718 719 // SetPointOffset is a threshold value to pull from the sensor 720 // to apply an offset. For upper thresholds this means the 721 // setpoint is usually negative. 722 auto findSetpointOffset = base.find("SetPointOffset"); 723 if (findSetpointOffset != base.end()) 724 { 725 offsetType = 726 std::get<std::string>(findSetpointOffset->second); 727 if (std::find(thresholds::types.begin(), 728 thresholds::types.end(), 729 offsetType) == thresholds::types.end()) 730 { 731 throw std::runtime_error("Unsupported type: " + 732 offsetType); 733 } 734 } 735 736 if (offsetType.empty()) 737 { 738 struct conf::ControllerInfo& info = 739 conf[std::get<std::string>(base.at("Name"))]; 740 info.inputs = std::move(inputs); 741 populatePidInfo(bus, base, info, nullptr); 742 } 743 else 744 { 745 // we have to split up the inputs, as in practice t-control 746 // values will differ, making setpoints differ 747 for (const std::string& input : inputs) 748 { 749 struct conf::ControllerInfo& info = conf[input]; 750 info.inputs.emplace_back(input); 751 populatePidInfo(bus, base, info, &offsetType); 752 } 753 } 754 } 755 } 756 auto findStepwise = 757 configuration.second.find(stepwiseConfigurationInterface); 758 if (findStepwise != configuration.second.end()) 759 { 760 const auto& base = findStepwise->second; 761 const std::vector<std::string>& zones = 762 std::get<std::vector<std::string>>(base.at("Zones")); 763 for (const std::string& zone : zones) 764 { 765 size_t index = getZoneIndex(zone, foundZones); 766 conf::PIDConf& conf = zoneConfig[index]; 767 768 std::vector<std::string> inputs; 769 std::vector<std::string> sensorNames = 770 std::get<std::vector<std::string>>(base.at("Inputs")); 771 772 bool sensorFound = false; 773 for (const std::string& sensorName : sensorNames) 774 { 775 std::string name = sensorName; 776 // replace spaces with underscores to be legal on dbus 777 std::replace(name.begin(), name.end(), ' ', '_'); 778 std::vector<std::pair<std::string, std::string>> 779 sensorPathIfacePairs; 780 781 if (!findSensors(sensors, name, sensorPathIfacePairs)) 782 { 783 break; 784 } 785 786 for (const auto& sensorPathIfacePair : sensorPathIfacePairs) 787 { 788 size_t idx = 789 sensorPathIfacePair.first.find_last_of("/") + 1; 790 std::string shortName = 791 sensorPathIfacePair.first.substr(idx); 792 793 inputs.push_back(shortName); 794 auto& config = sensorConfig[shortName]; 795 config.readPath = sensorPathIfacePair.first; 796 config.type = "temp"; 797 config.ignoreDbusMinMax = true; 798 // todo: maybe un-hardcode this if we run into slower 799 // timeouts with sensors 800 801 config.timeout = 0; 802 sensorFound = true; 803 } 804 } 805 if (!sensorFound) 806 { 807 continue; 808 } 809 struct conf::ControllerInfo& info = 810 conf[std::get<std::string>(base.at("Name"))]; 811 info.inputs = std::move(inputs); 812 813 info.type = "stepwise"; 814 info.stepwiseInfo.ts = 1.0; // currently unused 815 info.stepwiseInfo.positiveHysteresis = 0.0; 816 info.stepwiseInfo.negativeHysteresis = 0.0; 817 std::string subtype = std::get<std::string>(base.at("Class")); 818 819 info.stepwiseInfo.isCeiling = (subtype == "Ceiling"); 820 auto findPosHyst = base.find("PositiveHysteresis"); 821 auto findNegHyst = base.find("NegativeHysteresis"); 822 if (findPosHyst != base.end()) 823 { 824 info.stepwiseInfo.positiveHysteresis = std::visit( 825 VariantToDoubleVisitor(), findPosHyst->second); 826 } 827 if (findNegHyst != base.end()) 828 { 829 info.stepwiseInfo.negativeHysteresis = std::visit( 830 VariantToDoubleVisitor(), findNegHyst->second); 831 } 832 std::vector<double> readings = 833 std::get<std::vector<double>>(base.at("Reading")); 834 if (readings.size() > ec::maxStepwisePoints) 835 { 836 throw std::invalid_argument("Too many stepwise points."); 837 } 838 if (readings.empty()) 839 { 840 throw std::invalid_argument( 841 "Must have one stepwise point."); 842 } 843 std::copy(readings.begin(), readings.end(), 844 info.stepwiseInfo.reading); 845 if (readings.size() < ec::maxStepwisePoints) 846 { 847 info.stepwiseInfo.reading[readings.size()] = 848 std::numeric_limits<double>::quiet_NaN(); 849 } 850 std::vector<double> outputs = 851 std::get<std::vector<double>>(base.at("Output")); 852 if (readings.size() != outputs.size()) 853 { 854 throw std::invalid_argument( 855 "Outputs size must match readings"); 856 } 857 std::copy(outputs.begin(), outputs.end(), 858 info.stepwiseInfo.output); 859 if (outputs.size() < ec::maxStepwisePoints) 860 { 861 info.stepwiseInfo.output[outputs.size()] = 862 std::numeric_limits<double>::quiet_NaN(); 863 } 864 } 865 } 866 } 867 if constexpr (DEBUG) 868 { 869 debugPrint(); 870 } 871 if (zoneConfig.empty() || zoneDetailsConfig.empty()) 872 { 873 std::cerr 874 << "No fan zones, application pausing until new configuration\n"; 875 return false; 876 } 877 return true; 878 } 879 } // namespace dbus_configuration 880