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