8 Commits

Author SHA1 Message Date
Tabascl
8ff1d8be9d Bump version numbers 2024-07-07 23:52:03 +02:00
Tabascl
78b2a62643 Remove aggregator, fix changing HWMON paths
Add logic to use actual device paths for the PWM sensors instead of their
standart HWMON paths.

This was done after noticing that due to the load order of kernel modules,
the index of HWMON paths could change.
2024-07-07 22:19:27 +02:00
e2509cea8b Various fixes
- Change unit path
- Rename method
- Make CurrentPWM method return its value in PWM instead of percent
2023-10-08 13:50:33 +02:00
0a6bab36be Hysteresis, Inhibit-Stop Timeframe 2023-08-28 14:55:07 +02:00
d1649b7de1 Bump version 2023-07-24 13:58:12 +02:00
ff72f8d2ea Add support for zero-fan mode, fix sensor identifiers 2023-07-24 13:55:53 +02:00
ad12f7a981 Switch to meson build system 2023-07-23 23:43:47 +02:00
33a760489e Remove NVIDIA stuff 2023-07-23 18:04:17 +02:00
22 changed files with 352 additions and 221 deletions

View File

@@ -1,19 +1,21 @@
pkgname=fantasize pkgname=fantasize
pkgver=0.1.9 pkgver=0.3.0
pkgrel=1 pkgrel=1
pkgdesc='C++ fan control for Linux' pkgdesc='C++ fan control for Linux'
url='https://github.com/Tabascl/fantasize.git' url='https://github.com/Tabascl/fantasize.git'
source=("$pkgname-$pkgver.tar.gz::https://github.com/Tabascl/fantasize/archive/refs/tags/v$pkgver.tar.gz") source=("$pkgname-$pkgver.tar.gz::https://github.com/Tabascl/fantasize/archive/refs/tags/v$pkgver.tar.gz")
arch=('x86_64') arch=('x86_64')
license=('GPL3') license=('GPL3')
makedepends=('git' 'cmake' 'nlohmann-json' 'boost' 'cuda') makedepends=('git' 'meson' 'nlohmann-json' 'boost')
sha256sums=('SKIP') sha256sums=('SKIP')
build() { build() {
cmake -S "$pkgname-$pkgver/app" -B build -DCMAKE_BUILD_TYPE=Release meson setup build "$pkgname-$pkgver/app" -Dbuildtype=Release
cmake --build build cd build
meson compile
} }
package() { package() {
cmake --install build --prefix "$pkgdir/" --verbose cd build
meson install
} }

View File

@@ -1,37 +0,0 @@
cmake_minimum_required(VERSION 3.0)
project(fantasize VERSION 0.1.8)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(nlohmann_json 3.11.2 REQUIRED)
find_package(Boost 1.74 COMPONENTS program_options log log_setup date_time REQUIRED)
add_executable(${PROJECT_NAME}
src/main.cxx
src/sensor/LMSensorsFacade.cxx
src/sensor/GPUSensorsFacade.cxx
src/sensor/Sensor.cxx
src/sensor/NvidiaSensor.cxx
src/sensor/LMSensor.cxx
src/pwm/PWMControl.cxx
src/pwm/PWMControlFacade.cxx
src/fan/HwmonFan.cxx
src/fan/FanCurve.cxx
src/fan/FanLabeler.cxx
src/fan/Aggregators.cxx
src/FanGenerator.cxx
src/Serializer.cxx
src/sensor/SensorManager.cxx
src/Controller.cxx
src/Settings.cxx
src/App.cxx
)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20)
target_include_directories(${PROJECT_NAME} PUBLIC include /opt/cuda)
target_link_libraries(${PROJECT_NAME} PUBLIC sensors nvidia-ml nlohmann_json::nlohmann_json tbb ${Boost_LIBRARIES})
install(TARGETS ${PROJECT_NAME} DESTINATION usr/local/bin)
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/unit/fantasize.service DESTINATION usr/lib/systemd/system)

View File

@@ -17,16 +17,16 @@
using json = nlohmann::json; using json = nlohmann::json;
class Serializer class Serializer {
{
public: public:
Serializer(); Serializer();
void SerializeFans(std::vector<std::shared_ptr<Fan>> fans); void SerializeFans(std::vector<std::shared_ptr<Fan>> fans);
std::vector<std::shared_ptr<Fan>> DeserializeFans( std::vector<std::shared_ptr<Fan>>
std::vector<std::shared_ptr<Sensor>> availableSensors); DeserializeFans(std::vector<std::shared_ptr<Sensor>> availableSensors);
std::vector<std::shared_ptr<FanCurve>> DeserializeFanCurves( std::vector<std::shared_ptr<FanCurve>>
std::vector<std::shared_ptr<Sensor>> availableSensors, DeserializeFanCurves(std::vector<std::shared_ptr<Sensor>> availableSensors,
std::vector<std::shared_ptr<Fan>> availableFans); std::vector<std::shared_ptr<Fan>> availableFans,
bool &everythingFound);
std::shared_ptr<Settings> DeserializeSettings(); std::shared_ptr<Settings> DeserializeSettings();
private: private:

View File

@@ -17,8 +17,13 @@ public:
virtual void StartPWM(int value) = 0; virtual void StartPWM(int value) = 0;
virtual int StartPWM() = 0; virtual int StartPWM() = 0;
virtual void ZeroFanModeSupported(bool value) = 0;
virtual bool ZeroFanModeSupported() = 0;
virtual void FindPWMLimits() = 0; virtual void FindPWMLimits() = 0;
virtual void AdjustPWMLimits() = 0; virtual void AdjustPWMLimits() = 0;
virtual void EnforceSetValue() = 0;
}; };
#endif // FAN_H_ #endif // FAN_H_

View File

@@ -8,30 +8,31 @@
#include <fan/HwmonFan.h> #include <fan/HwmonFan.h>
#include <sensor/Sensor.h> #include <sensor/Sensor.h>
struct FanStep struct FanStep {
{
int Temp; int Temp;
int Percent; int Percent;
}; };
class FanCurve class FanCurve {
{
public: public:
FanCurve(std::vector<FanStep> steps, FanCurve(std::vector<FanStep> steps,
std::vector<std::shared_ptr<Sensor>> sensors, std::vector<std::shared_ptr<Sensor>> sensors,
std::vector<std::shared_ptr<Fan>> fans, std::vector<std::shared_ptr<Fan>> fans,
std::unique_ptr<Aggregator> aggregator); std::unique_ptr<Aggregator> aggregator, int hysteresis);
void DoFanControl(); void DoFanControl();
private: private:
int AggregateTemperature();
void PrintInfo(); void PrintInfo();
bool ExceedsHysteresis(int temperature);
void ApplyFanPower(std::shared_ptr<Fan> fan, int targetFanPower);
std::vector<FanStep> mSteps; std::vector<FanStep> mSteps;
std::vector<std::shared_ptr<Sensor>> mTempSensors; std::vector<std::shared_ptr<Sensor>> mTempSensors;
std::vector<std::shared_ptr<Fan>> mFans; std::vector<std::shared_ptr<Fan>> mFans;
std::unique_ptr<Aggregator> mAggregator; std::unique_ptr<Aggregator> mAggregator;
int mHystersis;
int mLastTemperature;
}; };
#endif // FANCURVE_H_ #endif // FANCURVE_H_

View File

@@ -24,21 +24,33 @@ public:
void StartPWM(int value) override; void StartPWM(int value) override;
int StartPWM() override; int StartPWM() override;
void ZeroFanModeSupported(bool value) override;
bool ZeroFanModeSupported() override;
void FindPWMLimits() override; void FindPWMLimits() override;
void AdjustPWMLimits() override; void AdjustPWMLimits() override;
void EnforceSetValue() override;
json toJson() const override; json toJson() const override;
const std::string toString() const override; const std::string toString() const override;
private: private:
bool InhibitStopPeriodExpired();
void SetPower(int percent);
std::shared_ptr<PWMControl> mPWMControl; std::shared_ptr<PWMControl> mPWMControl;
std::shared_ptr<Sensor> mRpmSensor; std::shared_ptr<Sensor> mRpmSensor;
std::string mLabel; std::string mLabel;
int mMinPWM; int mMinPWM = 0;
int mStartPWM; int mStartPWM = 0;
bool mZeroFanModeSupported = false;
std::chrono::time_point<std::chrono::steady_clock> mLastAdjustmentTime; std::chrono::time_point<std::chrono::steady_clock> mLastAdjustmentTime;
std::chrono::time_point<std::chrono::steady_clock> mLastStartTime;
int mSetValue = 0;
bool mWasStopped = false;
}; };
#endif // HWMONFAN_H_ #endif // HWMONFAN_H_

View File

@@ -12,11 +12,11 @@ enum class PWM_MODE { DC = 0, PWM };
class PWMControl : public Printable, public Serializable { class PWMControl : public Printable, public Serializable {
public: public:
PWMControl(std::string controlPath); PWMControl(std::string controlPath, int deviceIndex);
~PWMControl(); ~PWMControl();
void Power(int percent); void SetPower(int percent);
int Power(); int CurrentPWM();
void EnableManualControl(); void EnableManualControl();
void Reset(); void Reset();
@@ -26,11 +26,11 @@ public:
json toJson() const override; json toJson() const override;
private: private:
int mCurrentValue; std::string mConfigPath;
std::string mControlPath; std::string mControlPath;
std::string mEnablePath; std::string mEnablePath;
std::string mModePath; std::string mModePath;
int mDeviceIndex;
std::string mInitialEnable; std::string mInitialEnable;
std::string mInitialMode; std::string mInitialMode;

View File

@@ -16,9 +16,13 @@ public:
std::vector<std::shared_ptr<Sensor>> TemperatureSensors(); std::vector<std::shared_ptr<Sensor>> TemperatureSensors();
std::vector<std::shared_ptr<Sensor>> RPMSensors(); std::vector<std::shared_ptr<Sensor>> RPMSensors();
void ReloadSensors();
private: private:
template <sensors_subfeature_type T> template <sensors_subfeature_type T>
std::vector<std::shared_ptr<Sensor>> Sensors(); std::vector<std::shared_ptr<Sensor>> Sensors();
void InitSensors();
void CleanupSensors();
private: private:
FILE *mConfigFile; FILE *mConfigFile;

View File

@@ -12,6 +12,8 @@ public:
std::vector<std::shared_ptr<Sensor>> TemperatureSensors(); std::vector<std::shared_ptr<Sensor>> TemperatureSensors();
std::vector<std::shared_ptr<Sensor>> RPMSensors(); std::vector<std::shared_ptr<Sensor>> RPMSensors();
void ReloadSensors();
private: private:
std::unique_ptr<LMSensorsFacade> mLMSensorsFacade; std::unique_ptr<LMSensorsFacade> mLMSensorsFacade;
std::unique_ptr<GPUSensorsFacade> mGPUSensorsFacade; std::unique_ptr<GPUSensorsFacade> mGPUSensorsFacade;

37
app/meson.build Normal file
View File

@@ -0,0 +1,37 @@
project('fantasize', 'cpp', version : '0.3.0', default_options : 'cpp_std=c++20')
src = [
'src/main.cxx',
'src/sensor/LMSensorsFacade.cxx',
'src/sensor/Sensor.cxx',
'src/sensor/LMSensor.cxx',
'src/pwm/PWMControl.cxx',
'src/pwm/PWMControlFacade.cxx',
'src/fan/HwmonFan.cxx',
'src/fan/FanCurve.cxx',
'src/fan/FanLabeler.cxx',
'src/fan/Aggregators.cxx',
'src/FanGenerator.cxx',
'src/Serializer.cxx',
'src/sensor/SensorManager.cxx',
'src/Controller.cxx',
'src/Settings.cxx',
'src/App.cxx'
]
deps = [
dependency('nlohmann_json'),
dependency('boost', modules : ['program_options', 'log', 'log_setup', 'date_time', 'thread']),
dependency('tbb'),
meson.get_compiler('cpp').find_library('sensors')
]
inc = include_directories('include')
exe = executable('fantasize',
src,
dependencies: deps,
install: true,
include_directories: inc)
install_data('unit/fantasize.service', install_dir : '/usr/lib/systemd/system')

View File

@@ -1,14 +1,35 @@
#include "fan/Fan.h"
#include "fan/FanCurve.h"
#include <chrono>
#include <execution> #include <execution>
#include <boost/log/attributes/named_scope.hpp>
#include <boost/log/trivial.hpp>
#include <App.h> #include <App.h>
#include <memory>
#include <thread>
using namespace std; using namespace std;
void App::Init() { void App::Init() {
mFans = mSerializer.DeserializeFans(mSensorManager.RPMSensors()); BOOST_LOG_FUNCTION();
bool everythingFound = false;
mFans = mSerializer.DeserializeFans(mSensorManager.RPMSensors());
auto fanCurves = mSerializer.DeserializeFanCurves( auto fanCurves = mSerializer.DeserializeFanCurves(
mSensorManager.TemperatureSensors(), mFans); mSensorManager.TemperatureSensors(), mFans, everythingFound);
while (!everythingFound) {
BOOST_LOG_TRIVIAL(info) << "Couldn't find every sensor. Retrying in 5s...";
this_thread::sleep_for(chrono::seconds(5));
mSensorManager.ReloadSensors();
mFans = mSerializer.DeserializeFans(mSensorManager.RPMSensors());
fanCurves = mSerializer.DeserializeFanCurves(
mSensorManager.TemperatureSensors(), mFans, everythingFound);
}
mSettings = mSerializer.DeserializeSettings(); mSettings = mSerializer.DeserializeSettings();

View File

@@ -8,7 +8,7 @@
#include <fan/HwmonFan.h> #include <fan/HwmonFan.h>
#include <pwm/PWMControl.h> #include <pwm/PWMControl.h>
#define SETTLE_TIMEOUT 5 #define SETTLE_TIMEOUT 30
using namespace std; using namespace std;
@@ -22,7 +22,7 @@ FanGenerator::FindFans(vector<shared_ptr<Sensor>> rpmSensors,
cout << "Setting all fans to maximum speed" << endl; cout << "Setting all fans to maximum speed" << endl;
for (auto c : pwmControls) { for (auto c : pwmControls) {
c->EnableManualControl(); c->EnableManualControl();
c->Power(100); c->SetPower(100);
} }
// Wait for fans to settle // Wait for fans to settle
@@ -41,7 +41,7 @@ FanGenerator::FindFans(vector<shared_ptr<Sensor>> rpmSensors,
for (auto c : pwmControls) { for (auto c : pwmControls) {
cout << "Setting " << c->toString() cout << "Setting " << c->toString()
<< " to 50% and wait for it to settle..." << endl; << " to 50% and wait for it to settle..." << endl;
c->Power(50); c->SetPower(50);
this_thread::sleep_for(chrono::seconds(SETTLE_TIMEOUT)); this_thread::sleep_for(chrono::seconds(SETTLE_TIMEOUT));
@@ -53,7 +53,7 @@ FanGenerator::FindFans(vector<shared_ptr<Sensor>> rpmSensors,
} }
cout << "Setting fan back to 100%" << endl; cout << "Setting fan back to 100%" << endl;
c->Power(100); c->SetPower(100);
} }
return mapping; return mapping;

View File

@@ -1,9 +1,11 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <memory> #include <memory>
#include <boost/log/attributes/named_scope.hpp>
#include <boost/log/trivial.hpp>
#include <Serializer.h> #include <Serializer.h>
#include <fan/HwmonFan.h> #include <fan/HwmonFan.h>
#include <pwm/PWMControl.h> #include <pwm/PWMControl.h>
@@ -13,16 +15,13 @@
using namespace std; using namespace std;
namespace fs = filesystem; namespace fs = filesystem;
Serializer::Serializer() Serializer::Serializer() {
{
if (!fs::exists(SERIALIZATION_DIR)) { if (!fs::exists(SERIALIZATION_DIR)) {
fs::create_directory(SERIALIZATION_DIR); fs::create_directory(SERIALIZATION_DIR);
} }
} }
void void Serializer::SerializeFans(vector<shared_ptr<Fan>> fans) {
Serializer::SerializeFans(vector<shared_ptr<Fan>> fans)
{
json obj; json obj;
for (auto f : fans) { for (auto f : fans) {
@@ -33,11 +32,12 @@ Serializer::SerializeFans(vector<shared_ptr<Fan>> fans)
} }
vector<shared_ptr<Fan>> vector<shared_ptr<Fan>>
Serializer::DeserializeFans(vector<shared_ptr<Sensor>> availableSensors) Serializer::DeserializeFans(vector<shared_ptr<Sensor>> availableSensors) {
{ BOOST_LOG_FUNCTION();
vector<shared_ptr<Fan>> fans; vector<shared_ptr<Fan>> fans;
// Create a for the sensors first, then searching becomes cheaper // Create a map for the sensors first, then searching becomes cheaper
map<string, shared_ptr<Sensor>> sensorMap; map<string, shared_ptr<Sensor>> sensorMap;
for (auto s : availableSensors) { for (auto s : availableSensors) {
sensorMap[s->toString()] = s; sensorMap[s->toString()] = s;
@@ -45,37 +45,43 @@ Serializer::DeserializeFans(vector<shared_ptr<Sensor>> availableSensors)
auto data = ReadJson(); auto data = ReadJson();
try { try {
for (auto& el : data["fans"].items()) { for (auto &el : data["fans"].items()) {
auto pwmControl = make_shared<PWMControl>(el.value()["PWMControl"]); auto pwmControl = make_shared<PWMControl>(el.value()["PWMControl"]["Path"], el.value()["PWMControl"]["Index"]);
auto rpmSensor = sensorMap[el.value()["LMSensor"]]; auto rpmSensor = sensorMap[el.value()["LMSensor"]];
int minPWM = el.value()["MinPWM"]; int minPWM = el.value()["MinPWM"];
int startPWM = el.value()["StartPWM"]; int startPWM = el.value()["StartPWM"];
string label = el.value()["Label"]; string label = el.value()["Label"];
bool zeroFan = el.value()["ZeroFan"];
auto fan = make_shared<HwmonFan>(pwmControl, rpmSensor); auto fan = make_shared<HwmonFan>(pwmControl, rpmSensor);
fan->MinPWM(minPWM); fan->MinPWM(minPWM);
fan->StartPWM(startPWM); fan->StartPWM(startPWM);
fan->Label(label); fan->Label(label);
fan->ZeroFanModeSupported(zeroFan);
fans.push_back(fan); fans.push_back(fan);
} }
} catch (const std::exception& e) { } catch (const std::exception &e) {
std::cout << "Deserialization error! Message: " << e.what() << std::endl; std::cout << "Deserialization error! Message: " << e.what() << std::endl;
} }
BOOST_LOG_TRIVIAL(trace) << "### Available Fans";
for (auto f : fans) {
BOOST_LOG_TRIVIAL(trace) << f->toString();
}
return fans; return fans;
} }
void void Serializer::WriteJson(json o) {
Serializer::WriteJson(json o)
{
json obj; json obj;
if (fs::exists(fs::path(SERIALIZATION_DIR) / FANS_JSON_FILENAME)) { if (fs::exists(fs::path(SERIALIZATION_DIR) / FANS_JSON_FILENAME)) {
obj = ReadJson(); obj = ReadJson();
} }
for (auto& [key, value] : o.items()) { for (auto &[key, value] : o.items()) {
obj[key] = value; obj[key] = value;
} }
@@ -83,18 +89,17 @@ Serializer::WriteJson(json o)
ostrm << obj.dump(2) << "\n"; ostrm << obj.dump(2) << "\n";
} }
json json Serializer::ReadJson() {
Serializer::ReadJson()
{
ifstream istrm(fs::path(SERIALIZATION_DIR) / FANS_JSON_FILENAME); ifstream istrm(fs::path(SERIALIZATION_DIR) / FANS_JSON_FILENAME);
return json::parse(istrm); return json::parse(istrm);
} }
vector<shared_ptr<FanCurve>> vector<shared_ptr<FanCurve>> Serializer::DeserializeFanCurves(
Serializer::DeserializeFanCurves(
std::vector<std::shared_ptr<Sensor>> availableSensors, std::vector<std::shared_ptr<Sensor>> availableSensors,
std::vector<std::shared_ptr<Fan>> availableFans) std::vector<std::shared_ptr<Fan>> availableFans, bool &everythingFound) {
{ BOOST_LOG_FUNCTION();
everythingFound = true;
auto data = ReadJson(); auto data = ReadJson();
map<string, shared_ptr<Sensor>> sensorMap; map<string, shared_ptr<Sensor>> sensorMap;
@@ -109,21 +114,26 @@ Serializer::DeserializeFanCurves(
vector<shared_ptr<FanCurve>> curves; vector<shared_ptr<FanCurve>> curves;
for (auto& el : data["fancurves"].items()) { for (auto &el : data["fancurves"].items()) {
vector<FanStep> steps; vector<FanStep> steps;
vector<shared_ptr<Sensor>> sensors; vector<shared_ptr<Sensor>> sensors;
vector<shared_ptr<Fan>> fans; vector<shared_ptr<Fan>> fans;
for (auto& step : el.value()["FanSteps"].items()) { for (auto &step : el.value()["FanSteps"].items()) {
steps.push_back(FanStep{ step.value()[0], step.value()[1] }); steps.push_back(FanStep{step.value()[0], step.value()[1]});
} }
for (auto& sensor : el.value()["Sensors"].items()) { for (auto &sensor : el.value()["Sensors"].items()) {
if (sensorMap.contains(sensor.value())) if (sensorMap.contains(sensor.value()))
sensors.push_back(sensorMap[sensor.value()]); sensors.push_back(sensorMap[sensor.value()]);
else {
BOOST_LOG_TRIVIAL(warning)
<< "Sensor " << sensor.value() << " not found!";
everythingFound = false;
}
} }
for (auto& fan : el.value()["Fans"].items()) { for (auto &fan : el.value()["Fans"].items()) {
if (fanMap.contains(fan.value())) if (fanMap.contains(fan.value()))
fans.push_back(fanMap[fan.value()]); fans.push_back(fanMap[fan.value()]);
} }
@@ -131,25 +141,24 @@ Serializer::DeserializeFanCurves(
std::unique_ptr<Aggregator> aggregator = std::unique_ptr<Aggregator> aggregator =
aggregatorFromString(el.value()["AggregateFunction"]); aggregatorFromString(el.value()["AggregateFunction"]);
curves.push_back( int hysteresis = el.value()["Hysteresis"];
make_shared<FanCurve>(steps, sensors, fans, std::move(aggregator)));
curves.push_back(make_shared<FanCurve>(steps, sensors, fans,
std::move(aggregator), hysteresis));
} }
return curves; return curves;
} }
std::unique_ptr<Aggregator> std::unique_ptr<Aggregator>
Serializer::aggregatorFromString(std::string str) const Serializer::aggregatorFromString(std::string str) const {
{
if (str == "max") if (str == "max")
return std::make_unique<MaxAggregator>(); return std::make_unique<MaxAggregator>();
else else
return std::make_unique<AverageAggregator>(); return std::make_unique<AverageAggregator>();
} }
shared_ptr<Settings> shared_ptr<Settings> Serializer::DeserializeSettings() {
Serializer::DeserializeSettings()
{
int frequency = FREQUENCY_DEFAULT; int frequency = FREQUENCY_DEFAULT;
auto data = ReadJson(); auto data = ReadJson();

View File

@@ -1,6 +1,6 @@
#include <boost/log/attributes/named_scope.hpp>
#include <iostream> #include <iostream>
#include <boost/log/attributes/named_scope.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <fan/FanCurve.h> #include <fan/FanCurve.h>
@@ -11,23 +11,27 @@ using namespace std;
FanCurve::FanCurve(std::vector<FanStep> steps, FanCurve::FanCurve(std::vector<FanStep> steps,
std::vector<std::shared_ptr<Sensor>> sensors, std::vector<std::shared_ptr<Sensor>> sensors,
std::vector<std::shared_ptr<Fan>> fans, std::vector<std::shared_ptr<Fan>> fans,
std::unique_ptr<Aggregator> aggregator) std::unique_ptr<Aggregator> aggregator, int hysteresis)
: mSteps(steps) : mSteps(steps), mTempSensors(sensors), mFans(fans),
, mTempSensors(sensors) mAggregator(std::move(aggregator)), mHystersis(hysteresis),
, mFans(fans) mLastTemperature(INT_MIN) {
, mAggregator(std::move(aggregator))
{
PrintInfo(); PrintInfo();
} }
void void FanCurve::DoFanControl() {
FanCurve::DoFanControl() BOOST_LOG_FUNCTION()
{
BOOST_LOG_FUNCTION();
int temp = AggregateTemperature(); BOOST_LOG_TRIVIAL(trace) << "## Fans in curve";
for (auto f : mFans)
BOOST_LOG_TRIVIAL(trace) << f->toString();
int t0, t1, p0, p1; BOOST_LOG_TRIVIAL(trace) << "## Sensors in curve";
for (auto s : mTempSensors)
BOOST_LOG_TRIVIAL(trace) << s->toString();
int temp = mAggregator->aggregate(mTempSensors);
int t0 = 0, t1 = 0, p0 = 0, p1 = 0;
int targetFanPower; int targetFanPower;
if (temp <= mSteps[0].Temp) { if (temp <= mSteps[0].Temp) {
@@ -35,7 +39,7 @@ FanCurve::DoFanControl()
} else if (temp > mSteps[mSteps.size() - 1].Temp) { } else if (temp > mSteps[mSteps.size() - 1].Temp) {
targetFanPower = mSteps[mSteps.size() - 1].Percent; targetFanPower = mSteps[mSteps.size() - 1].Percent;
} else { } else {
for (int i = 0; i < mSteps.size(); i++) { for (int i = 0; i < (int)mSteps.size(); i++) {
if (temp > mSteps[i].Temp) { if (temp > mSteps[i].Temp) {
t0 = mSteps[i].Temp; t0 = mSteps[i].Temp;
p0 = mSteps[i].Percent; p0 = mSteps[i].Percent;
@@ -48,26 +52,26 @@ FanCurve::DoFanControl()
targetFanPower = p0 + ((p1 - p0) / (t1 - t0)) * (temp - t0); targetFanPower = p0 + ((p1 - p0) / (t1 - t0)) * (temp - t0);
} }
BOOST_LOG_TRIVIAL(trace) << "Current temp: " << temp;
BOOST_LOG_TRIVIAL(trace) << "Last temp: " << mLastTemperature;
BOOST_LOG_TRIVIAL(trace) << "# Hysteresis check";
if (ExceedsHysteresis(temp)) {
BOOST_LOG_TRIVIAL(trace) << "passed";
for (auto f : mFans) { for (auto f : mFans) {
if (f->RPM() <= 0) { ApplyFanPower(f, targetFanPower);
BOOST_LOG_TRIVIAL(warning) << "Fan stopped completely!";
f->PWM(f->StartPWM()); mLastTemperature = temp;
f->AdjustPWMLimits(); }
} else { } else {
f->PWM(targetFanPower); for (auto f : mFans)
} f->EnforceSetValue();
BOOST_LOG_TRIVIAL(trace) << "not passed";
} }
} }
int void FanCurve::PrintInfo() {
FanCurve::AggregateTemperature()
{
return mAggregator->aggregate(mTempSensors);
}
void
FanCurve::PrintInfo()
{
BOOST_LOG_FUNCTION() BOOST_LOG_FUNCTION()
BOOST_LOG_TRIVIAL(info) << "### Fan curve:"; BOOST_LOG_TRIVIAL(info) << "### Fan curve:";
@@ -103,3 +107,22 @@ FanCurve::PrintInfo()
BOOST_LOG_TRIVIAL(info) << sStream.str(); BOOST_LOG_TRIVIAL(info) << sStream.str();
} }
bool FanCurve::ExceedsHysteresis(int temperature) {
int lowThreshold = mLastTemperature - mHystersis;
int highThreshold = mLastTemperature + mHystersis;
return temperature <= lowThreshold || temperature >= highThreshold;
}
void FanCurve::ApplyFanPower(std::shared_ptr<Fan> fan, int targetFanPower) {
BOOST_LOG_FUNCTION();
if (!fan->ZeroFanModeSupported() && fan->RPM() <= 0) {
BOOST_LOG_TRIVIAL(warning) << "Fan " << fan->toString() << " stopped completely!";
fan->PWM(fan->StartPWM());
fan->AdjustPWMLimits();
} else {
fan->PWM(targetFanPower);
}
}

View File

@@ -16,7 +16,7 @@ void FanLabeler::RunFanLabelInteraction(
cout << endl; cout << endl;
for (auto f : fans) { for (auto f : fans) {
cout << "Setting fan to max power" << endl; cout << "Setting fan " << f->toString() << " to max power" << endl;
f->PWM(100); f->PWM(100);

View File

@@ -1,3 +1,4 @@
#include <chrono>
#include <iostream> #include <iostream>
#include <ostream> #include <ostream>
#include <thread> #include <thread>
@@ -9,6 +10,7 @@
#include <pwm/PWMControl.h> #include <pwm/PWMControl.h>
#define TIMEOUT 10 #define TIMEOUT 10
#define INHIBIT_STOP_PERIOD 120
#define STEP 2 #define STEP 2
using namespace std; using namespace std;
@@ -21,10 +23,38 @@ HwmonFan::HwmonFan(std::shared_ptr<PWMControl> pwmControl,
void HwmonFan::PWM(int percent) { void HwmonFan::PWM(int percent) {
if (percent < mMinPWM) { if (percent < mMinPWM) {
mPWMControl->Power(mMinPWM); if (mZeroFanModeSupported && InhibitStopPeriodExpired()) {
SetPower(percent);
mWasStopped = true;
} else { } else {
mPWMControl->Power(percent); SetPower(mMinPWM);
} }
} else {
if (mWasStopped) {
mWasStopped = false;
mLastStartTime = chrono::steady_clock::now();
SetPower(mStartPWM);
} else {
SetPower(percent);
}
}
}
bool HwmonFan::InhibitStopPeriodExpired() {
BOOST_LOG_FUNCTION();
auto result = chrono::steady_clock::now() - mLastStartTime >
chrono::duration(chrono::seconds(INHIBIT_STOP_PERIOD));
BOOST_LOG_TRIVIAL(trace) << "Inhibit-Stop period expired:"
<< (result ? "true" : "false");
return result;
}
void HwmonFan::SetPower(int percent) {
mPWMControl->SetPower(percent);
mSetValue = percent;
} }
int HwmonFan::RPM() { return mRpmSensor->value(); } int HwmonFan::RPM() { return mRpmSensor->value(); }
@@ -39,6 +69,12 @@ void HwmonFan::StartPWM(int value) { mStartPWM = value; }
int HwmonFan::StartPWM() { return mStartPWM; } int HwmonFan::StartPWM() { return mStartPWM; }
void HwmonFan::ZeroFanModeSupported(bool value) {
mZeroFanModeSupported = value;
}
bool HwmonFan::ZeroFanModeSupported() { return mZeroFanModeSupported; }
void HwmonFan::FindPWMLimits() { void HwmonFan::FindPWMLimits() {
cout << "Looking for minimal PWM" << endl; cout << "Looking for minimal PWM" << endl;
int minPWM = 0; int minPWM = 0;
@@ -93,13 +129,13 @@ void HwmonFan::AdjustPWMLimits() {
} }
} }
void HwmonFan::EnforceSetValue() { mPWMControl->SetPower(mSetValue); }
json HwmonFan::toJson() const { json HwmonFan::toJson() const {
json obj; json obj;
obj = {mPWMControl->toJson(), obj = {mPWMControl->toJson(), mRpmSensor->toJson(),
mRpmSensor->toJson(), {"Label", mLabel}, {"MinPWM", mMinPWM},
{"Label", mLabel}, {"StartPWM", mStartPWM}, {"ZeroFan", mZeroFanModeSupported}};
{"MinPWM", mMinPWM},
{"StartPWM", mStartPWM}};
return obj; return obj;
} }
@@ -107,6 +143,6 @@ const string HwmonFan::toString() const {
if (!mLabel.empty()) { if (!mLabel.empty()) {
return mLabel; return mLabel;
} else { } else {
return "fan:" + mPWMControl->toString(); return mPWMControl->toString();
} }
} }

View File

@@ -20,24 +20,16 @@
#include <App.h> #include <App.h>
#define PROJECT_VERSION "v0.1.9" #define PROJECT_VERSION "v0.3.0"
namespace po = boost::program_options; namespace po = boost::program_options;
namespace logging = boost::log; namespace logging = boost::log;
App app; App app;
static int doInitialSetup = 0; void signal_handler(int s) { app.Shutdown(); }
void void InitLogging(bool verbose) {
signal_handler(int s)
{
app.Shutdown();
}
void
InitLogging(bool verbose)
{
logging::add_console_log( logging::add_console_log(
std::clog, std::clog,
logging::keywords::format = logging::keywords::format =
@@ -66,9 +58,7 @@ InitLogging(bool verbose)
} }
} }
int int main(int argc, char **argv) {
main(int argc, char** argv)
{
BOOST_LOG_FUNCTION() BOOST_LOG_FUNCTION()
BOOST_LOG_TRIVIAL(info) << "Version: " << PROJECT_VERSION; BOOST_LOG_TRIVIAL(info) << "Version: " << PROJECT_VERSION;
@@ -77,8 +67,8 @@ main(int argc, char** argv)
po::options_description desc("Allowed options"); po::options_description desc("Allowed options");
desc.add_options()("help,h", "produce help message")( desc.add_options()("help,h", "produce help message")(
"setup,s", po::bool_switch(), "run initial setup")( "setup,s", po::bool_switch(),
"verbose,v", po::bool_switch(), "print debug info"); "run initial setup")("verbose,v", po::bool_switch(), "print debug info");
po::variables_map vm; po::variables_map vm;
po::store(po::parse_command_line(argc, argv, desc), vm); po::store(po::parse_command_line(argc, argv, desc), vm);
@@ -98,7 +88,7 @@ main(int argc, char** argv)
app.Init(); app.Init();
app.NormalOperation(); app.NormalOperation();
} }
} catch (const std::exception& e) { } catch (const std::exception &e) {
std::cout << "An exception was caught: " << e.what() << std::endl; std::cout << "An exception was caught: " << e.what() << std::endl;
} }

View File

@@ -1,8 +1,8 @@
#include <boost/log/attributes/named_scope.hpp>
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <boost/log/attributes/named_scope.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <pwm/PWMControl.h> #include <pwm/PWMControl.h>
@@ -15,19 +15,27 @@
using namespace std; using namespace std;
namespace fs = filesystem; namespace fs = filesystem;
PWMControl::PWMControl(string controlPath) PWMControl::PWMControl(string controlPath, int deviceIndex)
: mControlPath(controlPath) : mConfigPath(controlPath), mDeviceIndex(deviceIndex) {
{ auto path = fs::path{controlPath};
fs::path pathEnable(mControlPath + PWM_POSTFIX_ENABLE);
fs::path pathMode(mControlPath + PWM_POSTFIX_MODE);
mEnablePath = pathEnable; int fileCount =
mModePath = pathMode; distance(fs::directory_iterator(path), fs::directory_iterator{});
if (fileCount != 1)
throw runtime_error("More than one HWMON device present, unsupported");
for (auto de : fs::directory_iterator(path)) {
auto testPath = de.path();
mControlPath =
fs::path{de.path() / (string("pwm").append(to_string(deviceIndex)))}
.string();
}
mEnablePath = fs::path{mControlPath + PWM_POSTFIX_ENABLE}.string();
mModePath = fs::path{mControlPath + PWM_POSTFIX_MODE}.string();
ifstream istrm; ifstream istrm;
mCurrentValue = Power();
istrm.open(mEnablePath); istrm.open(mEnablePath);
istrm >> mInitialEnable; istrm >> mInitialEnable;
istrm.close(); istrm.close();
@@ -37,55 +45,44 @@ PWMControl::PWMControl(string controlPath)
istrm.close(); istrm.close();
} }
PWMControl::~PWMControl() PWMControl::~PWMControl() {
{
BOOST_LOG_FUNCTION(); BOOST_LOG_FUNCTION();
BOOST_LOG_TRIVIAL(trace) << "Cleanup"; BOOST_LOG_TRIVIAL(trace) << "Cleanup";
Reset(); Reset();
} }
void void PWMControl::SetPower(int percent) {
PWMControl::Power(int percent)
{
BOOST_LOG_FUNCTION(); BOOST_LOG_FUNCTION();
int pwmValue = (PWM_MAX_VALUE * percent) / 100; int pwmValue = (PWM_MAX_VALUE * percent) / 100;
if (percent != mCurrentValue) { if (pwmValue != CurrentPWM()) {
BOOST_LOG_TRIVIAL(trace) << "Updating control value of " << toString() BOOST_LOG_TRIVIAL(trace) << "Updating control value of " << toString()
<< " to " << percent << "% (" << pwmValue << ")"; << " to " << percent << "% (" << pwmValue << ")";
ofstream ostrm(mControlPath, ios::trunc); ofstream ostrm(mControlPath, ios::trunc);
ostrm << pwmValue; ostrm << pwmValue;
ostrm.close(); ostrm.close();
mCurrentValue = percent;
} }
} }
int int PWMControl::CurrentPWM() {
PWMControl::Power()
{
int value; int value;
ifstream istrm; ifstream istrm;
istrm.open(mControlPath); istrm.open(mControlPath);
istrm >> value; istrm >> value;
return (value * 100) / PWM_MAX_VALUE; return value;
} }
void void PWMControl::EnableManualControl() {
PWMControl::EnableManualControl()
{
ofstream ostrm(mEnablePath, ios::trunc); ofstream ostrm(mEnablePath, ios::trunc);
ostrm << static_cast<int>(PWM_ENABLE::MANUAL_CONTROL); ostrm << static_cast<int>(PWM_ENABLE::MANUAL_CONTROL);
ostrm.close(); ostrm.close();
} }
void void PWMControl::Reset() {
PWMControl::Reset()
{
ofstream ostrm(mEnablePath, ios::trunc); ofstream ostrm(mEnablePath, ios::trunc);
ostrm << mInitialEnable; ostrm << mInitialEnable;
@@ -97,15 +94,9 @@ PWMControl::Reset()
ostrm.close(); ostrm.close();
} }
const string const string PWMControl::toString() const { return fs::path(mControlPath); }
PWMControl::toString() const
{
return fs::path(mControlPath).filename();
}
json json PWMControl::toJson() const {
PWMControl::toJson() const json obj = {"PWMControl", {{"Path", mConfigPath}, {"Index", mDeviceIndex}}};
{
json obj = { "PWMControl", mControlPath };
return obj; return obj;
} }

View File

@@ -1,6 +1,7 @@
#include <filesystem> #include <filesystem>
#include <iostream> #include <iostream>
#include <regex> #include <regex>
#include <string>
#include <pwm/PWMControlFacade.h> #include <pwm/PWMControlFacade.h>
@@ -20,14 +21,20 @@ vector<shared_ptr<PWMControl>> PWMControlFacade::PWMControls() {
for (const fs::directory_entry &hwmon_device : for (const fs::directory_entry &hwmon_device :
fs::directory_iterator{HWMON_BASE_PATH}) { fs::directory_iterator{HWMON_BASE_PATH}) {
// Resolve symlink to get device path instead of hwmon path, the latter of
// which can change
fs::path actual_path =
fs::canonical(HWMON_BASE_PATH / fs::read_symlink(hwmon_device));
for (const fs::directory_entry &control : for (const fs::directory_entry &control :
fs::directory_iterator{hwmon_device}) { fs::directory_iterator{actual_path}) {
auto filename = control.path().filename().string(); auto filename = control.path().filename().string();
if (regex_match(filename, re_ctrl)) { if (regex_match(filename, re_ctrl)) {
auto controlPath = control.path().string(); auto controlIndex = filename.back() - '0';
auto controlPath = actual_path.parent_path();
controls.push_back(make_shared<PWMControl>(controlPath)); controls.push_back(make_shared<PWMControl>(controlPath, controlIndex));
} }
} }
} }

View File

@@ -1,3 +1,5 @@
#include <iostream>
#include <boost/json/kind.hpp> #include <boost/json/kind.hpp>
#include <sensor/LMSensor.h> #include <sensor/LMSensor.h>
#include <sensors/sensors.h> #include <sensors/sensors.h>
@@ -17,7 +19,9 @@ int LMSensor::value() {
} }
const string LMSensor::toString() const { const string LMSensor::toString() const {
return sensors_get_label(mChipName, mFeature); ostringstream os;
os << mChipName->prefix << "." << sensors_get_label(mChipName, mFeature);
return os.str();
} }
json LMSensor::toJson() const { json LMSensor::toJson() const {

View File

@@ -12,12 +12,10 @@ using namespace std;
#define CONFIG_FILE "/etc/conf.d/sensors" #define CONFIG_FILE "/etc/conf.d/sensors"
LMSensorsFacade::LMSensorsFacade() : mConfigFile(fopen(CONFIG_FILE, "r")) { LMSensorsFacade::LMSensorsFacade() : mConfigFile(fopen(CONFIG_FILE, "r")) {
if (sensors_init(mConfigFile) != 0) { InitSensors();
throw runtime_error("Config file doesn't exist");
}
} }
LMSensorsFacade::~LMSensorsFacade() { sensors_cleanup(); } LMSensorsFacade::~LMSensorsFacade() { CleanupSensors(); }
std::vector<std::shared_ptr<Sensor>> LMSensorsFacade::TemperatureSensors() { std::vector<std::shared_ptr<Sensor>> LMSensorsFacade::TemperatureSensors() {
return Sensors<SENSORS_SUBFEATURE_TEMP_INPUT>(); return Sensors<SENSORS_SUBFEATURE_TEMP_INPUT>();
@@ -48,3 +46,16 @@ std::vector<std::shared_ptr<Sensor>> LMSensorsFacade::Sensors() {
return sensors; return sensors;
} }
void LMSensorsFacade::ReloadSensors() {
CleanupSensors();
InitSensors();
}
void LMSensorsFacade::InitSensors() {
if (sensors_init(mConfigFile) != 0) {
throw runtime_error("Config file doesn't exist");
}
}
void LMSensorsFacade::CleanupSensors() { sensors_cleanup(); }

View File

@@ -1,5 +1,8 @@
#include <sensor/SensorManager.h> #include <sensor/SensorManager.h>
#include <boost/log/attributes/named_scope.hpp>
#include <boost/log/trivial.hpp>
using namespace std; using namespace std;
SensorManager::SensorManager() SensorManager::SensorManager()
@@ -7,12 +10,20 @@ SensorManager::SensorManager()
mGPUSensorsFacade(make_unique<GPUSensorsFacade>()) {} mGPUSensorsFacade(make_unique<GPUSensorsFacade>()) {}
vector<shared_ptr<Sensor>> SensorManager::TemperatureSensors() { vector<shared_ptr<Sensor>> SensorManager::TemperatureSensors() {
BOOST_LOG_FUNCTION();
vector<shared_ptr<Sensor>> tempSensors; vector<shared_ptr<Sensor>> tempSensors;
tempSensors = mLMSensorsFacade->TemperatureSensors(); tempSensors = mLMSensorsFacade->TemperatureSensors();
auto gpuSensors = mGPUSensorsFacade->TemperatureSensors(); BOOST_LOG_TRIVIAL(trace) << "### Temperature sensors:";
tempSensors.insert(tempSensors.end(), gpuSensors.begin(), gpuSensors.end()); for (auto s : tempSensors) {
BOOST_LOG_TRIVIAL(trace) << s->toString();
}
// auto gpuSensors = mGPUSensorsFacade->TemperatureSensors();
// tempSensors.insert(tempSensors.end(), gpuSensors.begin(),
// gpuSensors.end());
return tempSensors; return tempSensors;
} }
@@ -20,3 +31,5 @@ vector<shared_ptr<Sensor>> SensorManager::TemperatureSensors() {
vector<shared_ptr<Sensor>> SensorManager::RPMSensors() { vector<shared_ptr<Sensor>> SensorManager::RPMSensors() {
return mLMSensorsFacade->RPMSensors(); return mLMSensorsFacade->RPMSensors();
} }
void SensorManager::ReloadSensors() { mLMSensorsFacade->ReloadSensors(); }