# Control 팬 제어, 시스템 모니터링, WiFi 제어, WakeLock, Web Push 알림을 제공하는 PHP 기반 단일 관리 패널입니다. ## 프로젝트 성격 Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를 웹에서 관리하기 위한 내부 운영 도구입니다. 로그인 후 대시보드에서 온도, 팬 RPM, PWM, WiFi client, 배터리, notice, dmesg, process 후보, Push 구독 상태를 확인하고 필요한 제어 명령을 실행합니다. 상태 갱신은 WebSocket을 우선 사용하고, 연결 실패 시 HTTP fallback으로 전환합니다. 팬 정책 적용과 센서 수집은 백그라운드 작업과 API 호출을 통해 DB에 기록됩니다. ## 주요 기능 - 팬 모드 `auto`, `manual`, `off` 제어 - PWM slider 기반 수동 팬 제어 - CPU/RP1 온도, 팬 RPM, 팬 효율, CPU 전력, 배터리 상태 차트 - 배터리 잔여 시간은 1S8P 병렬팩 `2200mAh x 8 = 17600mAh`, nominal `3.7V`, 총 `65.12Wh` 기준으로 계산 - 라즈베리파이 저전압/스로틀링 현재 상태, 최근 감지 시각, 지속시간 표시 - WiFi client 목록과 2.4G/5G client 수 표시 - WiFi client 상태 표시 - 5G 외부 WiFi 모듈이 연결 시간을 제공하지 않는 경우 서버 관측 기반 연결 시간 보정 - System Notice와 process CPU/MEM 후보 표시 - dmesg 로그 열람 - Web Push 구독 등록, 자동 복구, 구독 기기 목록 표시 - Push 기기 건강 상태 추적과 헬스체크 수동/자동 실행 - WakeLock 버튼으로 대시보드 화면 꺼짐 방지 - Reboot 버튼으로 2단계 확인 후 시스템 재부팅 요청 - 기본 브라우저 alert/confirm/prompt 대신 대시보드 디자인에 맞춘 custom dialog 사용 - custom dialog는 Enter/Escape 키 입력을 자체 처리하며, Reboot 확인 흐름은 중복 실행을 차단 - Translate 버튼으로 `en`, `ko` UI 언어 토글. 버튼 표시는 상태 suffix 없이 고정 - Theme 버튼으로 `dark`, `light` UI 테마 토글. 버튼 표시는 상태 suffix 없이 고정 ## 주요 API - `public/api.php?action=status`: 팬, 센서, 시스템, WiFi, history, notice 상태 조회 - `public/api.php?action=collect`: 센서 snapshot 수집 후 팬 정책 적용 - `public/api.php?action=fan`: 팬 모드와 PWM 저장/적용 - `public/api.php?action=wifi`: 허용된 WiFi/DHCP service 제어 - `public/api.php?action=push_devices`: Push 구독 기기 목록 조회 - `public/api.php?action=push_status`: 브라우저 구독 endpoint의 서버 등록 상태 조회 - `public/api.php?action=send_push_healthcheck`: 등록된 Push 기기 전체에 상태 확인 알림 발송 - `public/api.php?action=delete_push_endpoint`: 현재 endpoint 구독 삭제 - `public/api.php?action=dmesg`: dmesg 로그 조회 - `public/api.php?action=reboot`: 확인 단어와 관리자 암호 재검증 후 sudo reboot 실행 ## 구성 - `public/index.php`: 로그인과 관리 화면 - `public/api.php`: 상태 조회와 조작 API - `public/assets/app.js`: 대시보드 렌더링, WebSocket, 차트, 조작 이벤트 - `public/assets/wakelock.js`: Screen Wake Lock API 제어 - `public/push_subscribe.js`: Push 구독 등록, 삭제, 자동 복구 - `public/sw.js`: Push service worker - `config/config.php`: DB, 인증, CSRF, Push, shell 실행 공통 함수 - `config/vapid.php`: 외부 secret 기반 VAPID 설정 bridge - `apply_policy.php`: CLI/cron 팬 정책 적용 - `bin/control_ws.php`: WebSocket 서버. 핵심 PHP 파일 변경 감지 시 종료되고 systemd가 재시작해 새 코드를 로드 - `bin/push_healthcheck.php`: 작업스케줄러용 Push 헬스체크 CLI - `bin/wifi_observe.php`: 5G WiFi 연결 시간 보정을 위한 시스템단 관측 CLI - `systemd/control-wifi-observe.*`: WiFi 관측 CLI를 주기적으로 실행하는 systemd unit 예시 ## 데이터/저장소 - `control_state`: 팬 모드와 PWM 상태 - `sensor_logs`: 온도, RPM, PWM, 배터리, load, memory, disk, uptime - `/tmp/control-low-voltage-state.json`: `vcgencmd get_throttled` 기반 저전압 감지 상태와 최근 지속시간 - `/tmp/control-throttling-state.json`: `vcgencmd get_throttled` 기반 스로틀링 감지 상태와 최근 지속시간 - `system_notice_state`: notice 활성 상태와 기준값 - `system_notice_logs`: notice 발생 이력 - `wifi_observed_sessions`: 5G WiFi client의 최초/마지막 감지 시간 - `push_subscriptions`: Web Push 구독 정보 - `push_event_logs`: Push 발송, 수신, 표시, 클릭, 실패 이벤트 - `/home/seo/secret/control.php`: 앱 비밀번호, DB 설정, VAPID key - 배터리 용량 설정은 `/home/seo/secret/control.php`의 `battery.cell_capacity_mah`, `battery.parallel_cells`, `battery.nominal_voltage`, `battery.capacity_wh`를 사용합니다. ## Push 누락 방지 구조 Control은 숨은 알림을 반복 발송하는 방식 대신 구독 자동 복구, 실제 수신 이벤트 기록, 발송 실패 추적, 저빈도 헬스체크를 조합합니다. ### 클라이언트 자동 복구 - 대시보드 로드 직후 Push 구독 상태를 확인합니다. - 화면이 다시 보일 때 구독 상태를 재확인합니다. - 5분마다 브라우저 구독과 서버 등록 상태를 비교합니다. - 브라우저 구독이 없으면 VAPID 공개키로 재구독합니다. - 브라우저 구독은 있으나 서버에 누락되어 있으면 다시 저장합니다. ### 서버 상태 추적 `push_subscriptions`에는 구독 정보와 함께 다음 상태가 저장됩니다. - `last_send_success_at`: 서버가 endpoint로 Push 발송에 성공한 시간 - `last_send_failed_at`: 서버 발송 실패 시간 - `last_received_at`: Service Worker가 `push` 이벤트를 실제 수신한 시간 - `last_notification_at`: 알림 표시 성공 시간 - `last_click_at`: 사용자 알림 클릭 시간 - `failure_count`: 연속 발송 실패 횟수 - `last_failure_reason`: 마지막 실패 사유 ### 상태 판정 - `healthy`: 최근 24시간 안에 실제 Push 수신 기록이 있음 - `watch`: 최근 7일 안에 수신 기록은 있지만 24시간을 초과함 - `stale`: 서버 발송 성공 기록은 있으나 장기간 실제 수신 기록이 없음 - `failed`: 연속 발송 실패가 3회 이상 누적됨 - `pending`: 등록은 되어 있으나 판단할 수 있는 수신/발송 기록이 부족함 ### 자동 헬스체크 이 장치에는 systemd timer가 등록되어 있습니다. ```bash control-push-healthcheck.timer control-push-healthcheck.service ``` 실행 명령은 다음과 같습니다. ```bash /usr/bin/php /var/www/control/bin/push_healthcheck.php --min-hours=24 ``` 타이머는 활성화 후 10분 뒤 첫 실행하고, 이후 service 실행 기준 24시간마다 실행합니다. 스크립트 내부에서도 `--min-hours=24` 쿨다운을 확인하므로 중복 실행되더라도 24시간 안이면 발송을 건너뜁니다. `silent: true`는 소리와 진동을 줄이는 옵션이며 완전히 보이지 않는 알림이 아닙니다. Web Push는 사용자에게 보이는 알림을 전제로 운영합니다. ## WiFi 관측 자동화 5G 외부 WiFi 모듈은 `iw station dump`에 `connected time`을 제공하지 않는 경우가 있어, 서버가 MAC별 최초 감지 시각을 DB에 저장하고 경과 시간을 계산합니다. 기존 대시보드 조회만으로도 보정은 가능하지만, 사용자가 접속하지 않은 시간에는 최초 감지가 늦어질 수 있습니다. 이를 막기 위해 다음 systemd timer가 10초마다 관측 CLI를 실행합니다. ```bash control-wifi-observe.timer control-wifi-observe.service ``` 실행 명령은 다음과 같습니다. ```bash /usr/bin/php /var/www/control/bin/wifi_observe.php ``` 이 작업은 팬 정책, 차트 로그 저장, Push 발송과 분리되어 있으며 WiFi client 목록 확인과 `wifi_observed_sessions` 갱신만 수행합니다. ## 처리 흐름 1. 화면 진입 시 로그인 세션과 remember token을 확인합니다. 2. 대시보드는 `status` snapshot을 렌더링합니다. 3. WebSocket 연결이 성공하면 `status` 메시지로 갱신하고, 실패하면 HTTP fallback을 사용합니다. 4. 팬 조작은 상태 저장, 정책 적용, 로그 저장 순서로 처리합니다. 5. WiFi client 목록은 `iw station dump`와 dnsmasq lease를 조합하고, 5G 연결 시간이 `N/A`이면 MAC 기준 최초 감지 시간을 DB에 저장해 경과 시간을 계산합니다. 6. `control-wifi-observe.timer`는 사용자 접속과 무관하게 10초마다 5G 관측 세션을 갱신합니다. 7. Push 등록은 권한 요청과 구독 저장이 끝나면 즉시 상태를 갱신합니다. 8. WakeLock 버튼은 활성 상태를 초록색 버튼으로 표시합니다. 9. Reboot 버튼은 `재부팅` 단어 입력과 관리자 암호 재입력을 모두 통과한 뒤 서버 API로 재부팅을 요청합니다. 확인 단어 입력창에서 Enter를 눌러도 같은 Reboot 흐름이 다시 열리지 않도록 키 이벤트 기본 동작과 중복 실행을 차단합니다. 10. Translate 버튼은 최초 접속 시 디바이스 언어를 기본값으로 사용하고, 변경값은 `localStorage`의 `controlLang`에 저장합니다. 라벨은 `Translate` 또는 `번역`으로만 표시합니다. 11. Theme 버튼은 기본값 `dark`로 시작하고, 변경값은 `localStorage`의 `controlTheme`에 저장합니다. 라벨은 `Theme` 또는 `테마`로만 표시합니다. 12. 시스템 상태는 `vcgencmd get_throttled`를 읽어 현재 저전압/스로틀링 여부와 부팅 후 이력, 최근 감지 시각, 현재/최근 지속시간을 표시합니다. 13. Push 발송, 수신, 표시, 클릭 이벤트를 기록하고 구독 DB의 건강 상태를 갱신합니다. ## 주요 함수/모듈 - `collect_snapshot()`: 센서와 fan 상태 snapshot 생성 - `throttled_statuses()`: 라즈베리파이 throttled flag를 읽고 저전압/스로틀링 감지 이력을 상태 파일로 추적 - `low_voltage_status()`: 기존 호출 호환을 위한 저전압 상태 wrapper - `apply_fan_policy()`: 팬 목표값 계산과 적용 - `json_out()`: API JSON 응답 표준화 - `apply_observed_wifi_connected_time()`: 5G WiFi 연결 시간이 없는 client에 서버 관측 경과 시간 적용 - `bin/wifi_observe.php`: 화면 접속 없이 `wifi_data()`를 호출해 5G 관측 세션을 선제 갱신 - `push_subscribe.js`: Push 구독 등록과 복구 - `push_health_summary()`: 상태별 Push 기기 수 집계 - `send_push_healthcheck_if_due()`: 쿨다운 기반 헬스체크 알림 발송 - `assets/wakelock.js`: WakeLock 버튼 상태와 Screen Wake Lock API 제어 - `customAlert()`, `customConfirm()`, `customPrompt()`: 대시보드 공통 확인/입력 dialog - `controlLang`, `controlTheme`: 재접속 후에도 유지되는 언어/테마 localStorage key - `BATTERY_CELL_CAPACITY_MAH`, `BATTERY_PARALLEL_CELLS`, `BATTERY_CAPACITY_MAH`, `BATTERY_NOMINAL_VOLTAGE`, `BATTERY_CAPACITY_WH`: 병렬 배터리팩 용량과 잔여시간 계산 기준 ## 보안 - 로그인 세션과 CSRF token을 사용합니다. - POST 조작 API는 CSRF 검증을 통과해야 합니다. - sysfs 쓰기와 systemctl 실행 권한은 sudoers 범위로 제한해야 합니다. - 재부팅 API는 CSRF, 로그인 세션, 확인 단어 `재부팅`, 관리자 암호 재검증을 모두 요구합니다. - VAPID key와 앱 비밀번호는 저장소 밖 secret 파일로 관리합니다. ## 운영 체크포인트 - 센서 수집 주기와 DB 증가량을 확인합니다. - 배터리팩 변경 시 `/home/seo/secret/control.php`의 `battery` 값을 먼저 갱신합니다. 현재 기준은 `2200mAh x 8 parallel = 17600mAh`, nominal `65.12Wh`입니다. - 하드웨어 또는 OS 변경 후 fan sysfs 경로를 확인합니다. - 2.4G 내장 WiFi는 `hostapd-24g.service` 개별 restart 직후 일부 IoT 단말이 WPA/EAPOL 재협상 루프에 들어갈 수 있으므로, 안정화된 상태에서는 개별 restart를 피하고 필요 시 전체 reboot 또는 채널 변경으로 재초기화합니다. - 2026-06-07 조사 기준 `disassoc_low_ack=1`은 로그에 직접 원인으로 남지 않았고, 채널 변경 후 단말이 안정적으로 붙은 상태에서는 제거하지 않고 관찰합니다. 동일 단말이 짧게 재연결을 반복하면 그때 `disassoc_low_ack` 비활성화를 검토합니다. - 2.4G WiFi 연결 시간은 내장 AP가 제공하는 `connected time`을 그대로 사용합니다. 2026-06-08 확인 기준 2.4G 연결 시간 누락은 없습니다. - 5G WiFi 연결 시간은 외부 모듈이 값을 제공하지 않을 때 서버가 처음 감지한 시각 기준으로 계산합니다. 현재 5G 목록에서 MAC이 사라지면 관측 세션을 즉시 종료하므로 재연결 시 0부터 다시 누적됩니다. - `control-wifi-observe.timer`는 10초마다 `/var/www/control/bin/wifi_observe.php`를 실행해 사용자가 대시보드에 접속하지 않아도 5G 연결 시간 카운터를 시작하고 유지합니다. 상태는 `systemctl status control-wifi-observe.timer`와 `/var/log/control-wifi-observe.log`에서 확인합니다. - WebSocket은 장기 실행 프로세스이므로 `public/api.php`, `config/config.php`, `bin/control_ws.php` 변경을 감지하면 15초 안에 종료되고 `control-websocket.service`가 새 프로세스로 재시작합니다. - WebSocket 장기 실행 중 DB 연결이 끊길 수 있으므로 reconnect 로그를 확인합니다. - Push 구독 자동 복구가 과도한 반복 등록을 만들지 않는지 확인합니다. - Push 헬스체크 timer 상태는 `systemctl list-timers --all control-push-healthcheck.timer`로 확인합니다. - `failed`, `stale` 기기는 알림 권한, 브라우저 데이터 삭제, PWA 재설치 여부를 확인합니다. - Reboot API 사용 전 웹 서버 실행 계정의 sudoers에 `/usr/sbin/reboot` 비밀번호 없는 실행 권한이 제한적으로 설정되어 있는지 확인합니다. - 언어/테마 표시가 예상과 다르면 브라우저 localStorage의 `controlLang`, `controlTheme` 값을 확인합니다.