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, nominal3.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,koUI 언어 토글. 버튼 표시는 상태 suffix 없이 고정 - Theme 버튼으로
dark,lightUI 테마 토글. 버튼 표시는 상태 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: 상태 조회와 조작 APIpublic/assets/app.js: 대시보드 렌더링, WebSocket, 차트, 조작 이벤트public/assets/wakelock.js: Screen Wake Lock API 제어public/push_subscribe.js: Push 구독 등록, 삭제, 자동 복구public/sw.js: Push service workerconfig/config.php: DB, 인증, CSRF, Push, shell 실행 공통 함수config/vapid.php: 외부 secret 기반 VAPID 설정 bridgeapply_policy.php: CLI/cron 팬 정책 적용bin/control_ws.php: WebSocket 서버. 핵심 PHP 파일 변경 감지 시 종료되고 systemd가 재시작해 새 코드를 로드bin/push_healthcheck.php: 작업스케줄러용 Push 헬스체크 CLIbin/wifi_observe.php: 5G WiFi 연결 시간 보정을 위한 시스템단 관측 CLIsystemd/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가 등록되어 있습니다.
control-push-healthcheck.timer
control-push-healthcheck.service
실행 명령은 다음과 같습니다.
/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를 실행합니다.
control-wifi-observe.timer
control-wifi-observe.service
실행 명령은 다음과 같습니다.
/usr/bin/php /var/www/control/bin/wifi_observe.php
이 작업은 팬 정책, 차트 로그 저장, Push 발송과 분리되어 있으며 WiFi client 목록 확인과 wifi_observed_sessions 갱신만 수행합니다.
처리 흐름
- 화면 진입 시 로그인 세션과 remember token을 확인합니다.
- 대시보드는
statussnapshot을 렌더링합니다. - WebSocket 연결이 성공하면
status메시지로 갱신하고, 실패하면 HTTP fallback을 사용합니다. - 팬 조작은 상태 저장, 정책 적용, 로그 저장 순서로 처리합니다.
- WiFi client 목록은
iw station dump와 dnsmasq lease를 조합하고, 5G 연결 시간이N/A이면 MAC 기준 최초 감지 시간을 DB에 저장해 경과 시간을 계산합니다. control-wifi-observe.timer는 사용자 접속과 무관하게 10초마다 5G 관측 세션을 갱신합니다.- Push 등록은 권한 요청과 구독 저장이 끝나면 즉시 상태를 갱신합니다.
- WakeLock 버튼은 활성 상태를 초록색 버튼으로 표시합니다.
- Reboot 버튼은
재부팅단어 입력과 관리자 암호 재입력을 모두 통과한 뒤 서버 API로 재부팅을 요청합니다. 확인 단어 입력창에서 Enter를 눌러도 같은 Reboot 흐름이 다시 열리지 않도록 키 이벤트 기본 동작과 중복 실행을 차단합니다. - Translate 버튼은 최초 접속 시 디바이스 언어를 기본값으로 사용하고, 변경값은
localStorage의controlLang에 저장합니다. 라벨은Translate또는번역으로만 표시합니다. - Theme 버튼은 기본값
dark로 시작하고, 변경값은localStorage의controlTheme에 저장합니다. 라벨은Theme또는테마로만 표시합니다. - 시스템 상태는
vcgencmd get_throttled를 읽어 현재 저전압/스로틀링 여부와 부팅 후 이력, 최근 감지 시각, 현재/최근 지속시간을 표시합니다. 웹 서버 실행 계정이/dev/vcio를 직접 읽지 못하면 기존 sudo 실행 경로로 한 번 더 조회합니다. - Push 발송, 수신, 표시, 클릭 이벤트를 기록하고 구독 DB의 건강 상태를 갱신합니다.
주요 함수/모듈
collect_snapshot(): 센서와 fan 상태 snapshot 생성read_throttled_flags():vcgencmd get_throttled를 직접 조회하고, 권한 문제로 실패하면 sudo 경유 조회를 시도throttled_statuses(): 라즈베리파이 throttled flag를 읽고 저전압/스로틀링 감지 이력을 상태 파일로 추적low_voltage_status(): 기존 호출 호환을 위한 저전압 상태 wrapperapply_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(): 대시보드 공통 확인/입력 dialogcontrolLang,controlTheme: 재접속 후에도 유지되는 언어/테마 localStorage keyBATTERY_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, nominal65.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비밀번호 없는 실행 권한이 제한적으로 설정되어 있는지 확인합니다. - 저전압/스로틀링이
N/A로 보이면 웹 서버 실행 계정의/dev/vcio접근 권한과/usr/bin/vcgencmd get_throttledsudoers 허용 여부를 확인합니다. - 언어/테마 표시가 예상과 다르면 브라우저 localStorage의
controlLang,controlTheme값을 확인합니다.