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