diff --git a/app/Command/Config.php b/app/Command/Config.php index b45bab7cf006923bc728c78e3084e54efc9690f7..172fac7f92350283566609d735f0d480a503d2a0 100644 --- a/app/Command/Config.php +++ b/app/Command/Config.php @@ -3,8 +3,10 @@ namespace App\Command; use App\Application; -use App\Model\Fan\HwmonFan; +use App\Model\Fan; +use App\Service\SystemCtl; use App\Service\Utils; +use App\Subroutines\Fan\EditFan; use App\Subroutines\InitializeConfig; use GetOpt\GetOpt; use Hugga\Console; @@ -34,6 +36,12 @@ class Config extends AbstractCommand return 1; } + $ranBefore = SystemCtl::isActive('fan-ctrl'); + if (!SystemCtl::stop('fan-ctrl')) { + $this->warn('fan-ctrl could not be stopped'); + return 1; + } + $fanConfig = $this->app->fanConfig; try { $fanConfig->readConfig(); @@ -60,28 +68,37 @@ class Config extends AbstractCommand $answer = $this->ask($actions); switch ($answer) { case 'edit a fan': - $fan = $this->selectFan($fanConfig->getFans()); + if ($fan = $this->selectFan($fanConfig->getFans())) { + $this->app->get(EditFan::class)->main($fan); + } break; + case 'exit without saving': break 2; + case 'save and exit': $fanConfig->save(); break 2; + default: $this->warn('not implemented'); break; } } while (true); - - // - edit a fan - // - add a sensor - // - edit a sensor - // - add a rule - // - edit a rule - // - save and leave - // - leave without saving - + if ($ranBefore) { + SystemCtl::start('fan-ctrl'); + } return 0; } + + protected function selectFan(array $fans): ?Fan + { + $choices = array_keys($fans); + $choices[] = 'cancel'; + $question = new Choice($choices); + $question->returnKey(); + $answer = $this->ask($question); + return $choices[$answer] === 'cancel' ? null : $fans[$choices[$answer]]; + } } diff --git a/app/FanConfig.php b/app/FanConfig.php index 57d16037e5bfd27316b6dd40de25d89eb5be17a7..9371be197679b2e64f3167a03f914411c95e370c 100644 --- a/app/FanConfig.php +++ b/app/FanConfig.php @@ -152,15 +152,7 @@ class FanConfig $console->info('Fans'); $table = new Table($console, array_map(function (Fan $fan) { if ($fan instanceof Fan\HwmonFan) { - $options = $fan->toArray()['options']; - return [ - $fan->name, - $options['hwmon'], - $options['fan'], - $options['pwm'], - $options['start'], - $options['max'], - ]; + return $fan->toRow(); } return [ diff --git a/app/Model/Fan.php b/app/Model/Fan.php index 557ea9237e0c9f4282e5e172909e7fb494b96484..982ad48513190c7e3f643174d2e93d7cdbb7d193 100644 --- a/app/Model/Fan.php +++ b/app/Model/Fan.php @@ -11,4 +11,6 @@ abstract class Fan extends Model abstract public function setSpeed($percentage); abstract public function stopFan(); + + abstract public function toRow(): array; } diff --git a/app/Model/Fan/HwmonFan.php b/app/Model/Fan/HwmonFan.php index 63a1dd56cac44be377ffb0277cff762c4809957b..4d1bc5d40fd3b86fbe386f401ffd5cef22db7fe5 100644 --- a/app/Model/Fan/HwmonFan.php +++ b/app/Model/Fan/HwmonFan.php @@ -2,8 +2,11 @@ namespace App\Model\Fan; +use App\Application; use App\Model\Concerns\DetectsHwmon; use App\Model\Fan; +use App\Service\Utils; +use Hugga\Console; class HwmonFan extends Fan { @@ -13,6 +16,8 @@ class HwmonFan extends Fan const ENABLE_MANUAL = 1; const ENABLE_MIN_AUTOMATIC = 2; + protected const UPDATE_INTERVAL = 1_600_000; // in microseconds + /** @var string */ protected $hwmon; @@ -72,12 +77,22 @@ class HwmonFan extends Fan return $this; } + public function getStartValue(): int + { + return $this->startValue; + } + public function setMaxValue(int $max): self { $this->maxValue = $max; return $this; } + public function getMaxValue(): int + { + return $this->maxValue; + } + public function setPwm(int $value) { if ($value < 0 or $value > 255) { @@ -87,6 +102,53 @@ class HwmonFan extends Fan file_put_contents($this->basePath . $this->pwm, $value); } + public function getPwm(): int + { + $pwm = file_get_contents($this->basePath . $this->pwm); + return (int)$pwm; + } + + public function setPwmAndWait(int $value): int + { + return $this->getPwm() > $value ? $this->decreasePwm($value) : $this->increasePwm($value); + } + + protected function decreasePwm(int $value): int + { + $this->setPwm($value); + usleep(self::UPDATE_INTERVAL); + if ($this->getPwm() !== $value) { + throw new \LogicException('This fan is controlled by an external application'); + } + + $currentSpeed = $this->getCurrentSpeed(); + do { + $lastSpeed = $currentSpeed; + usleep(self::UPDATE_INTERVAL); + $currentSpeed = $this->getCurrentSpeed(); + } while ($currentSpeed < $lastSpeed); + + return $this->getCurrentSpeed(); + } + + protected function increasePwm(int $value): int + { + $this->setPwm($value); + usleep(self::UPDATE_INTERVAL); + if ($this->getPwm() !== $value) { + throw new \LogicException('This fan is controlled by an external application'); + } + + $currentSpeed = $this->getCurrentSpeed(); + do { + $lastSpeed = $currentSpeed; + usleep(self::UPDATE_INTERVAL); + $currentSpeed = $this->getCurrentSpeed(); + } while ($currentSpeed > $lastSpeed); + + return $this->getCurrentSpeed(); + } + public function setSpeed($percentage) { $this->setPwm($this->startValue + ($this->maxValue - $this->startValue) * $percentage / 100); @@ -112,6 +174,18 @@ class HwmonFan extends Fan ]; } + public function toRow(): array + { + return [ + $this->name, + $this->hwmon, + $this->fan, + $this->pwm, + $this->startValue, + $this->maxValue, + ]; + } + protected function updateCurrentState() { $this->currentState = [ diff --git a/app/Service/SystemCtl.php b/app/Service/SystemCtl.php new file mode 100644 index 0000000000000000000000000000000000000000..c6345d6c31c2a66587c591a9eb04b9e714905516 --- /dev/null +++ b/app/Service/SystemCtl.php @@ -0,0 +1,29 @@ +<?php + +namespace App\Service; + +class SystemCtl +{ + public static function isActive(string $service): bool + { + return trim(exec('systemctl is-active ' . escapeshellarg($service))) === 'active'; + } + + public static function stop(string $service): bool + { + $status = trim(exec('systemctl is-active ' . escapeshellarg($service))); + if ($status === 'active') { + exec('systemctl stop ' . escapeshellarg($service)); + } + return trim(exec('systemctl is-active ' . escapeshellarg($service))) === 'inactive'; + } + + public static function start(string $service): bool + { + $status = trim(exec('systemctl is-active ' . escapeshellarg($service))); + if ($status === 'inactive') { + exec('systemctl start ' . escapeshellarg($service)); + } + return trim(exec('systemctl is-active ' . escapeshellarg($service))) === 'active'; + } +} diff --git a/app/Service/Utils.php b/app/Service/Utils.php deleted file mode 100644 index e7b376566f3d833f9f042c24321261684726a25a..0000000000000000000000000000000000000000 --- a/app/Service/Utils.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php - -namespace App\Service; - -use App\Application; -use App\Model\Fan\HwmonFan; -use Hugga\Console; - -class Utils -{ - public static function waitForStableRpm(HwmonFan $fan): int - { - static $waitTime = 600_000; // in micro seconds - static $count = 4; - $readings = []; - - // get the first readings - for ($i = 0; $i < $count; $i++) { - $readings[] = $fan->getCurrentSpeed(); - usleep($waitTime); - } - $min = min($readings); - $max = max($readings); - $average = array_sum($readings) / $count; - $variance = $average > 0 ? ($max - $min) / $average * 100 : 0; - - // wait till variance is less than 2 - while (array_sum($readings) > 0 && // fan has not stopped - $variance > 2 - ) { - Application::console()->line(sprintf( - 'min: %d rpm, max: %d rpm, avg: %d rpm, variance: %f%%', - $min, - $max, - $average, - $variance - ), Console::WEIGHT_DEBUG); - array_shift($readings); - $readings[] = $fan->getCurrentSpeed(); - $min = min($readings); - $max = max($readings); - $average = array_sum($readings) / $count; - $variance = $average > 0 ? ($max - $min) / $average * 100 : 0; - usleep($waitTime); - } - return $average; - } -} diff --git a/app/Subroutines/Fan/EditFan.php b/app/Subroutines/Fan/EditFan.php new file mode 100644 index 0000000000000000000000000000000000000000..0fec16bed130f29a9a81dcbbbf4c15f16b58672b --- /dev/null +++ b/app/Subroutines/Fan/EditFan.php @@ -0,0 +1,82 @@ +<?php + +namespace App\Subroutines\Fan; + +use App\Application; +use App\Concerns\InteractiveConsole; +use App\Model\Fan\HwmonFan; +use App\Service\Utils; +use Hugga\Console; +use Hugga\Input\Question\Choice; +use Hugga\Input\Question\Confirmation; + +class EditFan +{ + use InteractiveConsole; + + /** @var Application */ + protected $app; + + public function __construct(Application $app, Console $console) + { + $this->app = $app; + $this->console = $console; + } + + public function main(HwmonFan $fan) + { + do { + $this->table([$fan->toRow()], ['Name', 'hwmon', 'Fan', 'PWM', 'start', 'max']); + $answer = $this->ask(new Choice([ + 'rename fan', + 'adjust start value', + 'define limit', + 'done / exit', + ], 'What to change?', 'done / exit')); + switch ($answer) { + case 'adjust start value': + $this->warn('Going to stop the fan. If you experience overheating shut stop the application' . + ' immediately by hitting ctrl+c'); + $speed = $fan->setPwmAndWait(0); + $this->line('at 0 pwm the fan runs at ' . $speed . ' rpm'); + + $startValue = $this->ask('What value to try?', $fan->getStartValue()); + do { + $speed = $fan->setPwmAndWait($startValue); + $this->line('at ' . $startValue . ' pwm the fan runs at ' . $speed . ' rpm'); + $answer = $this->ask('Try another value or keep this value?', 'keep'); + } while ($answer !== 'keep' && $startValue = $answer); + + $fan->resetState(); + $fan->setStartValue($startValue); + break; + + case 'define limit': + $this->info('Accelerating fan to current limit...'); + $maxValue = $fan->getMaxValue(); + $startValue = $fan->getStartValue(); + + do { + $speed = $fan->setPwmAndWait($maxValue); + $this->line('at ' . $maxValue . ' pwm the fan runs at ' . $speed . ' rpm'); + $answer = $this->ask( + 'Try another value (between ' . $startValue . ' and 255) or keep this value?', + 'keep' + ); + } while ($answer !== 'keep' && $maxValue = $answer); + + $fan->resetState(); + $fan->setMaxValue($maxValue); + break; + + case 'rename fan': + $name = $this->ask('New name: ', $fan->name); + $fan->name = $name; + break; + + case 'done / exit': + return 0; + } + } while (true); + } +} diff --git a/app/Subroutines/InitializeConfig.php b/app/Subroutines/InitializeConfig.php index 3669f76812e130be65e48e28f006dd55c94cd0f0..cc312ab0956cac23349fa4ecfdec887cb7aa61db 100644 --- a/app/Subroutines/InitializeConfig.php +++ b/app/Subroutines/InitializeConfig.php @@ -6,6 +6,7 @@ use App\Application; use App\Concerns\InteractiveConsole; use App\Model\Fan\HwmonFan; use App\Service\Utils; +use App\Subroutines\Fan\EditFan; use Hugga\Console; use Hugga\Input\Question\Choice; use Hugga\Input\Question\Confirmation; @@ -17,9 +18,6 @@ class InitializeConfig /** @var Application */ protected $app; - /** - * @param Application $app - */ public function __construct(Application $app, Console $console) { $this->app = $app; @@ -38,47 +36,24 @@ class InitializeConfig $this->info('Fan ' . $fan->name); $start = $this->detectStartValue($fan); $fan->setStartValue($start); + $this->table([$fan->toRow()], ['Name', 'hwmon', 'Fan', 'PWM', 'start', 'max']); do { $answer = $this->ask(new Choice([ - 'name fan', - 'adjust start value', - 'define limit', + 'edit fan', 'add fan', - 'skip fan', - ], 'What next?', 'name fan')); + 'skip fan (don\'t include fan in config)', + ], 'What next?', 'edit fan')); switch ($answer) { - case 'adjust start value': - do { - $fan->setPwm(0); - $startValue = $this->ask('What value to try?', $start); - $fan->setPwm($startValue); - } while (!$this->ask(new Confirmation('Use that value?'))); - $fan->resetState(); - $fan->setStartValue($startValue); - break; - case 'define limit': - do { - $fan->setPwm(255); - $maxValue = $this->ask('What value to try?', 255); - $fan->setPwm($maxValue); - Utils::waitForStableRpm($fan); - $this->line('fan runs at ' . $fan->getCurrentSpeed() . ' rpm now'); - } while (!$this->ask(new Confirmation('Use that value?'))); - $fan->resetState(); - $fan->setMaxValue($maxValue); - break; - case 'name fan': - $name = $this->ask('Name of the fan: ', $fan->name); - $fan->name = $name; + case 'edit fan': + $this->app->get(EditFan::class)->main($fan); break; + case 'add fan': $fanConfig->addFan($fan); break 2; + case 'skip fan': break 2; - default: - $this->warn('not implemented'); - break; } } while (true); } @@ -181,8 +156,7 @@ class InitializeConfig protected function testPwm(HwmonFan $fan): array { - $fan->setPwm(255); - Utils::waitForStableRpm($fan); + $fan->setPwmAndWait(255); $fullSpeed = $fan->getCurrentSpeed(); if ($fullSpeed === 0) { @@ -190,8 +164,7 @@ class InitializeConfig return [0, 0]; } - $fan->setPwm(0); - Utils::waitForStableRpm($fan); + $fan->setPwmAndWait(0); $stopSpeed = $fan->getCurrentSpeed(); $fan->resetState(); @@ -201,14 +174,12 @@ class InitializeConfig protected function detectStartValue(HwmonFan $fan): int { $this->info('Detecting start value for ' . $fan->name); - $fan->setPwm(0); - Utils::waitForStableRpm($fan); + $fan->setPwmAndWait(0); $minSpeed = $fan->getCurrentSpeed() * 1.1; $pwm = 0; do { - $fan->setPwm($pwm += 5); - Utils::waitForStableRpm($fan); + $fan->setPwmAndWait($pwm += 5); $currentSpeed = $fan->getCurrentSpeed(); $this->line(sprintf('%d rpm with pwm value %d', $currentSpeed, $pwm)); } while ($minSpeed >= $currentSpeed);