diff --git a/README.md b/README.md index ea0921c..428dcc3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를 웹에서 관리하기 위한 내부 운영 도구입니다. 로그인 후 대시보드에서 온도, 팬 RPM, PWM, WiFi client, 저전압/스로틀링, notice를 확인하고 필요한 제어 명령을 실행합니다. 첫 화면은 운영 판단에 필요한 핵심 정보 위주로 구성하고, 상세 차트와 진단성 데이터는 접힘 영역에 둡니다. -상태 갱신은 WebSocket을 우선 사용하고, 연결 실패 시 HTTP fallback으로 전환합니다. 팬 정책 적용과 센서 수집은 백그라운드 작업과 API 호출을 통해 DB에 기록됩니다. +상태 갱신은 WebSocket을 우선 사용하고, 연결 실패 시 HTTP fallback으로 전환합니다. 팬 정책 적용과 센서 수집은 백그라운드 daemon과 API 호출을 통해 DB에 기록됩니다. ## 주요 기능 @@ -64,7 +64,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를 - `public/assets/app.js`: 대시보드 렌더링, WebSocket, 차트, 조작 이벤트 - `public/assets/wakelock.js`: Screen Wake Lock API 제어 - `config/config.php`: DB, 인증, CSRF, shell 실행 공통 함수 -- `apply_policy.php`: CLI/cron 팬 정책 적용 +- `apply_policy.php`: CLI 1회 실행과 daemon 모드를 지원하는 팬 정책 적용 루프 - `bin/control_ws.php`: WebSocket 서버. 핵심 PHP 파일 변경 감지 시 종료되고 systemd가 재시작해 새 코드를 로드 - `bin/wifi_observe.php`: 5G WiFi 연결 시간 보정을 위한 시스템단 관측 CLI - `touch_panel/panel.py`: 라즈베리파이 정품 터치 디스플레이용 standalone Qt 대시보드 @@ -72,6 +72,7 @@ Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를 - `touch_panel/control_touch_bridge.php`: 이전 Qt 패널용 로컬 JSON 브리지. 현재 standalone 패널은 사용하지 않습니다. - `touch_panel/control-touch-panel.desktop`: GUI 로그인 후 Qt 패널을 띄우는 데스크톱 autostart 파일 - `systemd/control-rpi-disk-temp.service`: 터치 패널용 SSD 온도 캐시 서비스 예시 +- `systemd/fanpanel-apply.service`: 팬 정책을 1초 간격으로 계속 실행하는 daemon unit 예시 - `systemd/control-wifi-observe.*`: WiFi 관측 CLI를 주기적으로 실행하는 systemd unit 예시 - `systemd/control-touch-panel.service`: GUI 세션에서 Qt 관리자 패널을 띄우는 systemd unit 예시 @@ -168,6 +169,7 @@ systemd 방식이 필요하면 `systemd/control-touch-panel.service`를 `/etc/sy - 탭 복귀: 즉시 1회 상태 갱신 - UPS/배터리 잔여 시간: 현재 운영 구성에서는 표시하지 않음 - dmesg: 진단 패널에서 열었을 때만 1초마다 갱신, 닫으면 중지 +- 팬 정책 daemon: `fanpanel-apply.service`가 `apply_policy.php --daemon`을 실행해 1초마다 팬 정책과 센서 로그를 처리 - WiFi 관측 보정 timer: 10초마다 `bin/wifi_observe.php` 실행 - 외출/귀가 팬 정책: 팬 정책 적용 루프에서 기본 30초마다 서울 HA의 휴대폰 device tracker를 확인 - WebSocket 소스 변경 감지: 15초마다 확인 후 재시작 @@ -205,7 +207,7 @@ control-wifi-observe.service /usr/bin/php /var/www/control/bin/wifi_observe.php ``` -이 작업은 팬 정책, 차트 로그 저장, System Notice 기록과 분리되어 있으며 WiFi/LAN client 목록 확인과 `wifi_observed_sessions` 갱신만 수행합니다. +이 작업은 팬 정책, 차트 로그 저장, System Notice 기록과 분리되어 있으며 WiFi/LAN client 목록 확인과 `wifi_observed_sessions` 갱신만 수행합니다. 정상 실행 JSON은 운영 기능에 쓰지 않으므로 systemd unit에서는 stdout을 버리고 실패 stderr만 journal에 남깁니다. 호스트명은 dnsmasq lease 값을 우선 사용합니다. lease hostname이 `N/A`이면 MAC 주소 끝 6자리 기반 임시명 `기기-XXXXXX`을 표시하고, 표의 호스트명을 클릭해 저장한 수동 이름은 `wifi_client_aliases`에 보관해 이후에도 우선 표시합니다. @@ -302,6 +304,7 @@ control-wifi-observe.service - 2.4G 내장 WiFi는 `hostapd-24g.service` 개별 restart 직후 일부 IoT 단말이 WPA/EAPOL 재협상 루프에 들어갈 수 있으므로, 안정화된 상태에서는 개별 restart를 피하고 필요 시 전체 reboot 또는 채널 변경으로 재초기화합니다. - `hostapd-24g.service`, `hostapd-5g.service`는 종료 후 해당 `wlan` 인터페이스를 `br0`에서 떼도록 두어, 모듈이 빠졌다가 돌아온 뒤 hostapd가 다시 붙이는 흐름으로 정리합니다. - 5G WiFi 연결 시간은 외부 모듈이 값을 제공하지 않을 때 서버가 처음 감지한 시각 기준으로 계산합니다. 현재 5G 목록에서 MAC이 사라지면 관측 세션을 즉시 종료하므로 재연결 시 0부터 다시 누적됩니다. +- `fanpanel-apply.service`는 `/var/www/control/apply_policy.php --daemon`으로 상주하며, 기존 1초 timer 방식과 같은 팬 정책/센서 로그 처리를 프로세스 재생성 없이 반복합니다. - `control-wifi-observe.timer`는 10초마다 `/var/www/control/bin/wifi_observe.php`를 실행해 사용자가 대시보드에 접속하지 않아도 5G/LAN 연결 시간 카운터를 시작하고 유지합니다. - WebSocket은 장기 실행 프로세스이므로 `public/api.php`, `config/config.php`, `bin/control_ws.php` 변경을 감지하면 15초 안에 종료되고 `control-websocket.service`가 새 프로세스로 재시작합니다. - WebSocket 장기 실행 중 DB 연결이 끊길 수 있으므로 reconnect 로그를 확인합니다. diff --git a/apply_policy.php b/apply_policy.php index 7a3a75b..4ef127b 100644 --- a/apply_policy.php +++ b/apply_policy.php @@ -3,6 +3,8 @@ declare(strict_types=1); require_once __DIR__ . '/config/config.php'; +const FAN_POLICY_INTERVAL_SECONDS = 1.0; + function local_fan_paths(): array { $candidates = glob('/sys/devices/platform/cooling_fan/hwmon/hwmon*/pwm1') ?: []; @@ -169,7 +171,8 @@ function dmesg_log_throttled(string $key, string $message, int $seconds = 30): v dmesg_log($message); } -try { +function run_fan_policy_once(): bool +{ sync_presence_fan_policy(); $state = get_control_state(); @@ -200,15 +203,12 @@ try { 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([ @@ -254,8 +254,11 @@ try { ); } - exit($ok ? 0 : 1); -} catch (Throwable $e) { + return $ok; +} + +function report_fan_policy_exception(Throwable $e): void +{ try { add_fan_action( 'apply_policy_exception', @@ -268,5 +271,34 @@ try { } fwrite(STDERR, $e->getMessage() . PHP_EOL); +} + +function run_fan_policy_daemon(): int +{ + while (true) { + $started = microtime(true); + + try { + run_fan_policy_once(); + } catch (Throwable $e) { + report_fan_policy_exception($e); + } + + $elapsed = microtime(true) - $started; + $sleep = max(0.05, FAN_POLICY_INTERVAL_SECONDS - $elapsed); + usleep((int)round($sleep * 1000000)); + } +} + +$daemon = in_array('--daemon', $argv ?? [], true); + +if ($daemon) { + exit(run_fan_policy_daemon()); +} + +try { + exit(run_fan_policy_once() ? 0 : 1); +} catch (Throwable $e) { + report_fan_policy_exception($e); exit(1); } diff --git a/systemd/control-wifi-observe.service b/systemd/control-wifi-observe.service index 1cc6319..840a0ac 100644 --- a/systemd/control-wifi-observe.service +++ b/systemd/control-wifi-observe.service @@ -8,5 +8,5 @@ User=root Group=root WorkingDirectory=/var/www/control ExecStart=/usr/bin/php /var/www/control/bin/wifi_observe.php -StandardOutput=append:/var/log/control-wifi-observe.log -StandardError=append:/var/log/control-wifi-observe.log +StandardOutput=null +StandardError=journal diff --git a/systemd/fanpanel-apply.service b/systemd/fanpanel-apply.service new file mode 100644 index 0000000..bfafca7 --- /dev/null +++ b/systemd/fanpanel-apply.service @@ -0,0 +1,19 @@ +[Unit] +Description=Apply fan panel policy daemon +After=network-online.target mariadb.service +Wants=network-online.target +StartLimitIntervalSec=0 + +[Service] +Type=simple +User=root +Group=root +WorkingDirectory=/var/www/control +ExecStart=/usr/bin/php /var/www/control/apply_policy.php --daemon +Restart=always +RestartSec=3 +StandardOutput=null +StandardError=journal + +[Install] +WantedBy=multi-user.target