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