Files
control/apply_policy.php
2026-06-07 00:33:58 +09:00

273 lines
6.4 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/config/config.php';
function local_fan_paths(): array
{
$candidates = glob('/sys/devices/platform/cooling_fan/hwmon/hwmon*/pwm1') ?: [];
$candidates = array_merge(
$candidates,
glob('/sys/class/hwmon/hwmon*/pwm1') ?: []
);
foreach ($candidates as $pwm) {
$dir = dirname($pwm);
return [
'base' => $dir,
'pwm' => $pwm,
'enable' => $dir . '/pwm1_enable',
'rpm' => $dir . '/fan1_input',
'name' => first_readable([$dir . '/name']) ?: 'cooling_fan',
];
}
return [
'base' => '',
'pwm' => '',
'enable' => '',
'rpm' => '',
'name' => '',
];
}
function local_temp(): float
{
$raw = first_readable([
'/sys/class/thermal/thermal_zone0/temp',
'/sys/devices/virtual/thermal/thermal_zone0/temp',
]);
if ($raw !== '' && is_numeric($raw)) {
return round(((float)$raw) / 1000, 2);
}
$vc = sh(['/usr/bin/vcgencmd', 'measure_temp'], false, 3)['out'];
if (preg_match('/([0-9.]+)/', $vc, $m)) {
return round((float)$m[1], 2);
}
return 0.0;
}
function local_target(float $temp): int
{
if ($temp >= 80) {
return 255;
}
if ($temp <= 50) {
return 0;
}
$ratio = ($temp - 50) / 30;
return max(0, min(255, (int)round($ratio * 255)));
}
function local_ramped_pwm(int $current, int $desired, float $temp): int
{
if ($temp >= 80) {
return $desired;
}
if ($desired > $current) {
return min($desired, $current + 2);
}
if ($desired < $current) {
return max($desired, $current - 2);
}
return $desired;
}
function local_mem(): array
{
$rows = [];
foreach (@file('/proc/meminfo', FILE_IGNORE_NEW_LINES) ?: [] as $line) {
if (preg_match('/^([^:]+):\s+(\d+)/', $line, $m)) {
$rows[$m[1]] = (int)$m[2];
}
}
$total = (int)($rows['MemTotal'] ?? 0);
$available = (int)($rows['MemAvailable'] ?? 0);
$used = max(0, $total - $available);
return [
'total_mb' => (int)round($total / 1024),
'used_mb' => (int)round($used / 1024),
'free_mb' => (int)round($available / 1024),
];
}
function local_disk(string $path = '/'): array
{
$total = @disk_total_space($path) ?: 0;
$free = @disk_free_space($path) ?: 0;
$used = max(0, $total - $free);
return [
'total_kb' => (int)round($total / 1024),
'used_kb' => (int)round($used / 1024),
'free_kb' => (int)round($free / 1024),
];
}
function local_uptime_seconds(): int
{
$raw = trim((string)@file_get_contents('/proc/uptime'));
if ($raw === '') {
return 0;
}
return (int)floor((float)explode(' ', $raw)[0]);
}
function read_int_file(string $file): int
{
if ($file === '' || !is_readable($file)) {
return 0;
}
$raw = trim((string)@file_get_contents($file));
return is_numeric($raw) ? (int)$raw : 0;
}
function write_sys_value(string $path, int $value): bool
{
if ($path === '') {
return false;
}
return @file_put_contents($path, $value . "\n", LOCK_EX) !== false;
}
function dmesg_log(string $message): void
{
$line = '<6>fanpanel: ' . $message . "\n";
@file_put_contents('/dev/kmsg', $line, FILE_APPEND);
}
function dmesg_log_throttled(string $key, string $message, int $seconds = 30): void
{
$file = '/tmp/fanpanel_dmesg_' . preg_replace('/[^a-zA-Z0-9_.-]/', '_', $key) . '.last';
$now = time();
$last = is_file($file) ? (int)trim((string)@file_get_contents($file)) : 0;
if (($now - $last) < $seconds) {
return;
}
@file_put_contents($file, (string)$now, LOCK_EX);
dmesg_log($message);
}
try {
$state = get_control_state();
$mode = (string)($state['mode'] ?? 'auto');
$manualPwm = max(0, min(255, (int)($state['manual_pwm'] ?? 120)));
if (!in_array($mode, ['auto', 'manual', 'off'], true)) {
$mode = 'auto';
}
$paths = local_fan_paths();
$temp = local_temp();
$currentPwm = read_int_file($paths['pwm']);
$desired = match ($mode) {
'manual' => $manualPwm,
'off' => 0,
default => local_target($temp),
};
$target = $mode === 'auto'
? local_ramped_pwm($currentPwm, $desired, $temp)
: $desired;
$enableOk = write_sys_value($paths['enable'], $mode === 'off' ? 0 : 1);
$pwmOk = write_sys_value($paths['pwm'], $target);
usleep(80000);
$actualPwm = read_int_file($paths['pwm']);
$rpm = read_int_file($paths['rpm']);
$load = sys_getloadavg() ?: [0, 0, 0];
$mem = local_mem();
$disk = local_disk('/');
$cpuPower = cpu_power_status();
$battery = battery_status();
$ok = $enableOk && $pwmOk;
add_sensor_log([
'cpu_temp_c' => $temp,
'fan_rpm' => $rpm,
'fan_efficiency' => fan_efficiency($rpm, $temp, $cpuPower['watts'] ?? null),
'rp1_temp_c' => rp1_temp_c(),
'cpu_voltage' => $cpuPower['voltage'],
'cpu_watts' => $cpuPower['watts'],
'battery_voltage' => $battery['voltage'],
'battery_percent' => $battery['percent'],
'pwm_value' => $actualPwm,
'pwm_percent' => round($actualPwm / 255 * 100, 2),
'pwm_mode' => $mode,
'cpu_load_1' => round((float)$load[0], 2),
'cpu_load_5' => round((float)$load[1], 2),
'cpu_load_15' => round((float)$load[2], 2),
'mem_total_mb' => $mem['total_mb'],
'mem_used_mb' => $mem['used_mb'],
'mem_free_mb' => $mem['free_mb'],
'disk_total_kb' => $disk['total_kb'],
'disk_used_kb' => $disk['used_kb'],
'disk_free_kb' => $disk['free_kb'],
'uptime_seconds' => local_uptime_seconds(),
'hostname' => gethostname() ?: null,
]);
send_battery_low_push_if_needed($battery);
if (!$ok) {
add_fan_action(
'apply_policy_failed',
$mode,
$target,
'enable=' . ($enableOk ? 'ok' : 'fail')
. ', pwm=' . ($pwmOk ? 'ok' : 'fail')
. ', path=' . ($paths['pwm'] ?: 'N/A'),
false
);
}
exit($ok ? 0 : 1);
} catch (Throwable $e) {
try {
add_fan_action(
'apply_policy_exception',
null,
null,
$e->getMessage(),
false
);
} catch (Throwable) {
}
fwrite(STDERR, $e->getMessage() . PHP_EOL);
exit(1);
}