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