Files
control/README.md
T

35 KiB

Control

팬 제어, 시스템 모니터링, WiFi 제어, WakeLock, Home Assistant 알림을 제공하는 PHP 기반 단일 관리 패널입니다.

프로젝트 성격

Control은 라즈베리파이/리눅스 호스트의 팬과 시스템 상태를 웹에서 관리하기 위한 내부 운영 도구입니다. 로그인 후 대시보드에서 온도, 팬 RPM, PWM, WiFi client, 배터리, notice를 확인하고 필요한 제어 명령을 실행합니다. 첫 화면은 운영 판단에 필요한 핵심 정보 위주로 구성하고, 상세 차트와 진단성 데이터는 접힘 영역에 둡니다.

상태 갱신은 WebSocket을 우선 사용하고, 연결 실패 시 HTTP fallback으로 전환합니다. 팬 정책 적용과 센서 수집은 백그라운드 작업과 API 호출을 통해 DB에 기록됩니다.

주요 기능

  • 팬 모드 auto, manual, off 제어
  • PWM slider 기반 수동 팬 제어
  • 오버레이 설정 모달에서 보안 정책, 팬 자동곡선, 이벤트 판정, 팬 이상감지, 배터리 예측 파라미터 조정
  • CPU 온도, 팬 RPM, 배터리 SOC, 배터리 전압 핵심 차트
  • RP1 온도, 팬 효율, CPU 전력, 잔여 시간 상세 차트는 접힘 영역에서 확인
  • 배터리 잔여 시간은 최근 24시간 SOC 방전 추세와 최대 45일 장기 학습 프로파일을 함께 사용하고, CPU 전력 변화로 현재 부하를 보정
  • 라즈베리파이 저전압/스로틀링 현재 상태, 복구 중/반복 발생 판정, 부팅 후 이력, 최근 감지 시각, 지속시간, 최근 10분 통계 표시
  • WiFi client 목록과 2.4G/5G client 수 표시
  • 5G 외부 WiFi 모듈이 연결 시간을 제공하지 않는 경우 서버 관측 기반 연결 시간 보정
  • WiFi 호스트명이 비어 있으면 MAC 기반 임시명을 표시하고, MAC별 수동 호스트명을 DB에 저장해 이후에도 유지
  • WiFi 신호/송신 속도/수신 속도가 비어 있으면 대역, dBm, 송수신 속도, 5G RTL8822BU 특성을 기준으로 결측값 보정
  • WiFi client, 주변 WiFi 스캔, 주변 Bluetooth 스캔의 신호는 7단계 한글 등급으로 표시하고, hover/focus/touch 시 커스텀 툴팁에서 실제 dBm을 표시
  • System Notice 최근 이력 표시
  • process CPU/MEM 후보는 프로세스 상세 접힘 영역에서 표시
  • /etc/systemd/system/*.service 기준 사용자 생성 서비스의 서비스명, 현재 상태, enable 상태, PID, restart 횟수, 최근 journal 로그를 진단 접힘 영역에서 표시
  • dmesg 로그는 진단 접힘 영역에서 필요할 때 열람
  • Home Assistant webhook 기반 Android 모바일 알림
  • 배터리 SOC 20% 이하 경고를 20/15/10/5% 단계로 구분하고 동일 tag로 갱신해 알림이 과도하게 쌓이지 않도록 처리
  • 배터리 SOC가 20%를 초과해 복구되면 같은 tag의 Android 알림을 clear_notification으로 제거
  • 최근 HA 알림 발송 성공/실패 이력은 진단 접힘 영역에 표시
  • WakeLock 버튼으로 대시보드 화면 꺼짐 방지
  • Reboot 버튼으로 2단계 확인 후 시스템 재부팅 요청
  • 기본 브라우저 alert/confirm/prompt 대신 대시보드 디자인에 맞춘 custom dialog 사용
  • custom dialog는 Enter/Escape 키 입력을 자체 처리하며, Reboot 확인 흐름은 중복 실행을 차단
  • Translate 버튼으로 en, ko UI 언어 토글
  • Theme 버튼으로 dark, light UI 테마 토글

주요 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=wifi_alias: MAC별 WiFi 호스트명 저장
  • public/api.php?action=settings: 설정 가능한 Control 동작값 조회
  • public/api.php?action=settings_save: 설정 모달 값 저장
  • public/api.php?action=settings_reset: 설정 모달 값을 기본값으로 초기화
  • 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 제어
  • config/config.php: DB, 인증, CSRF, HA 알림, shell 실행 공통 함수
  • apply_policy.php: CLI/cron 팬 정책 적용
  • bin/control_ws.php: WebSocket 서버. 핵심 PHP 파일 변경 감지 시 종료되고 systemd가 재시작해 새 코드를 로드
  • bin/control_scan_refresh.php: 주변 WiFi/Bluetooth 스캔 캐시를 백그라운드에서 갱신하는 CLI
  • bin/ha_notify_channel.php: Android 알림 채널 제거 명령 전송용 운영 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 기반 저전압 감지 상태, 최근 지속시간, 최근 episode 이력
  • /tmp/control-throttling-state.json: vcgencmd get_throttled 기반 스로틀링 감지 상태, 최근 지속시간, 최근 episode 이력
  • system_notice_state: notice 활성 상태와 기준값
  • system_notice_logs: notice 발생 이력
  • wifi_observed_sessions: 5G WiFi client의 최초/마지막 감지 시간
  • wifi_scan_observations: 주변 WiFi AP의 BSSID별 마지막 성공 스캔 관측값, 원문, 파싱 JSON
  • bluetooth_scan_observations: 주변 Bluetooth 장치의 주소별 마지막 성공 스캔 관측값, 원문, 파싱 JSON
  • wifi_client_aliases: MAC별 수동 WiFi 호스트명
  • app_settings: 설정 모달에서 저장한 Control 동작값 오버라이드
  • ha_notify_logs: HA webhook 알림 발송 성공/실패 이력, 대상 서버, HTTP code, tag, 메타 정보
  • /tmp/control_wifi_scan.json: 주변 WiFi 스캔 결과 파일 캐시
  • /tmp/control_bluetooth_scan.json: 주변 Bluetooth 스캔 결과 파일 캐시
  • /home/seo/secret/control.php: 앱 비밀번호, DB 설정, 배터리 설정, HA 알림 설정
  • 배터리 용량 설정은 /home/seo/secret/control.phpbattery.cell_capacity_mah, battery.parallel_cells, battery.nominal_voltage, battery.capacity_wh를 사용합니다.

배터리 잔여 시간 계산

잔여 시간은 단순히 현재 CPU 전력 평균만으로 계산하지 않습니다. 실제 배터리 SOC가 DB에서 어떤 속도로 떨어졌는지를 먼저 사용합니다.

  1. sensor_logs의 최근 24시간 배터리 기록을 1분 단위로 SQL 집계합니다.
  2. 30분, 1시간, 3시간, 6시간, 12시간, 24시간 창을 각각 검사합니다.
  3. 각 창의 시작/끝 SOC는 평균 한 점이 아니라 가장자리 구간의 중앙값으로 잡아 순간 튐을 줄입니다.
  4. 45분, 2시간, 4시간, 8시간, 16시간, 24시간 창은 가중 선형회귀로 SOC 기울기를 다시 계산합니다.
  5. 회귀 후보는 중앙절대편차 기준으로 잔차가 큰 SOC 튐을 제거한 뒤 한 번 더 회귀해 기울기를 보정합니다.
  6. 전압도 1분 집계 기록에서 가중 회귀로 기울기를 계산하고, 현재 전압에서 동적 하한 전압까지 남은 시간을 별도 후보로 만듭니다.
  7. SOC가 충분히 줄어든 방전 구간만 후보로 사용하고, 충전 또는 회복처럼 보이는 구간은 제외합니다.
  8. 최대 45일 기록은 약 5분 간격으로 샘플링해 SOC 구간별 장기 방전 프로파일과 SOC 1%당 실제 사용 Wh를 학습합니다.
  9. 장기 프로파일은 현재 SOC와 가까운 구간, 최근에 관측된 구간, 샘플 수가 많은 구간에 더 높은 가중치를 줍니다.
  10. 최근 CPU 전력이 각 후보 구간의 평균 전력보다 높거나 낮으면 방전 속도에 완만하게 반영해 현재 부하 변화를 보정합니다.
  11. 단기 중앙값 후보, 강건 회귀 후보, 전압 기울기 후보, 에너지 학습 후보, 용량 전력 후보, 장기 학습 후보를 신뢰도와 안정도 가중치로 합산합니다.
  12. 후보끼리 방전 속도가 크게 어긋나면 중앙값 기준으로 튄 후보를 제외하거나 가중치를 낮춥니다.
  13. 최종 신뢰도는 후보별 신뢰도 가중 평균에 후보 간 방전 속도 분산을 반영해 계산합니다.
  14. 용량 기반 후보와 fallback은 설정된 최소 시스템 전력보다 낮은 순간 전력값을 그대로 믿지 않고 최소 전력으로 끌어올려 계산합니다.
  15. 1s8p 18650 배터리팩에서 비현실적으로 긴 값이 나오지 않도록 잔여시간 현실 상한을 100% 기준 최대 시간으로 두고, 현재 SOC에 비례해 최종 후보를 제한합니다.
  16. 상한을 넘은 후보는 버리지 않고 상한값으로 눌러 반영하며, 신뢰도와 가중치를 낮춰 다른 실제 방전 후보를 더 우선하게 합니다.
  17. SOC 추세가 부족하면 마지막 대체값으로 배터리 용량 BATTERY_CAPACITY_WH와 최근 CPU 전력의 절사 평균을 사용합니다.

WebSocket 장기 실행 프로세스에서는 1분 단위 단기 집계 결과를 55초 동안, 약 5분 간격 장기 학습 샘플을 10분 동안 재사용해 DB 부담을 줄입니다. 장기 학습 샘플은 파일 캐시로도 보관해 다른 PHP 프로세스에서도 빠르게 재사용합니다.

대시보드의 잔여 시간 값에는 Control UI 스타일의 커스텀 계산 근거 팝오버가 붙습니다. 기본 브라우저 title tooltip은 사용하지 않으며, 마우스 hover 또는 키보드 focus 중에도 상태 갱신이 들어오면 팝오버 내용과 위치가 즉시 갱신됩니다. SOC 다중 시간창 추세로 표시되면 실제 DB 방전 속도 기반이며, 계산 출처에 장기 학습이 포함되면 누적 기록에서 학습한 SOC/부하 구간별 방전 경험도 반영된 상태입니다. 배터리 용량과 최근 전력 평균이면 SOC 추세가 부족해 용량 기반 fallback이 사용된 상태입니다.

팝오버는 계산 잔여 시간, 현실 상한, 상한 전 계산값, 시간당 SOC 감소율, 추세 신뢰도, 후보 수, 후보 분산, 최근 CPU 전력, 계산 출처, 반영 구간을 표시합니다. 각 후보별로 최근 30분/1시간/3시간/6시간/12시간/24시간 감소율, 신뢰도, 부하 보정 배율을 표시하고, 강건 회귀 후보는 튐 제거 후 남은 유효점 비율과 후보 안정도를 함께 표시합니다. 전압 후보는 하한 전압과 시간당 전압 하강량을, 에너지 학습 후보는 SOC 1%당 실제 Wh를, 장기 학습 후보는 반영된 학습 구간 수와 SOC 구간 수를 함께 표시합니다.

잔여 시간 차트의 마지막 지점은 현재 snapshot의 battery.remaining.seconds와 동기화합니다. DB에 저장된 마지막 센서 기록이 몇 초 늦어도 좌측 상태 영역과 차트의 최신 잔여 시간이 서로 다른 값을 보여주지 않도록, API 응답 history 끝에 현재 snapshot row를 반영합니다.

설정 모달

상단 설정 버튼은 전체 화면 오버레이 모달을 엽니다. 설정값은 app_settings 테이블에 저장되며, 저장 직후 다음 snapshot부터 반영됩니다. 민감정보가 들어 있는 /home/seo/secret/control.php는 직접 수정하지 않고 DB 오버라이드만 사용합니다.

설정 가능한 항목은 다음 범위입니다.

  • 팬 자동 제어: 팬 시작 온도, 최대 온도, 즉시 최대 PWM 온도, 자동 상승/하강 PWM step
  • 보안 정책: 자동 로그인 유지 기간, remember 쿠키 Secure/SameSite, CSRF 길이, remember 토큰 길이, User-Agent/IP 접두사 검증, 로그인 실패 잠금, 재부팅 허용 여부, 재부팅 확인 문구, 명령 timeout, WiFi 조작 허용 여부
  • WiFi/Bluetooth 진단: 주변 WiFi AP와 Bluetooth 장치 스캔 켜기/끄기, 스캔 캐시 시간, 명령 timeout, 표시 개수, 원문 일부 표시 여부
  • 알림 정책: 배터리 긴급/위험/경고/복구 기준, 배터리 낮음/복구 알림 쿨다운, 시스템 유의사항 알림 쿨다운
  • 화면/진단 표시: 프로세스 후보 수, 사용자 서비스 로그 줄 수, 사용자 서비스 캐시 시간, 팬 이상 이력 수, HA 알림 이력 수
  • 저전압/스로틀링: 최근 판정창, 복구 중 유지 시간, 반복 발생 기준 횟수
  • 팬 이상감지: 기준 표본 수, 최신 표본 제외 수, RPM/온도 이상 감지 차이, RPM/온도 복구 차이, RPM 감시 시작 PWM, alert 유지 중 반복 기록, 반복 기록 간격, 하강 변화 원인 후보 표시 여부
  • 배터리 예측: 단기/장기 학습 범위, 후보 필터 비율, 부하 보정 강도, 전압 하한, 용량 모델 가중치, 잔여시간 현실 상한, 최소 시스템 전력, 상한 초과 후보 감쇠, 에너지/전압 모델 사용 여부, 상태/잔여시간 차트 표본 수

설정 모달이 열려 있는 동안 1초 상태 갱신은 폼 DOM을 다시 만들지 않습니다. 입력 중인 값은 저장, 기본값 초기화, 닫기 전까지 유지되며, 백그라운드 snapshot은 내부 설정 payload만 갱신합니다. 단순 boolean 설정은 켜기/끄기 세그먼트로 표시합니다.

갱신 주기

  • WebSocket 상태 갱신: 1초마다
  • WebSocket 최초 연결: 즉시 1회 상태 전송
  • WebSocket 끊김 시 HTTP fallback 상태 갱신: 2초마다
  • 탭 복귀: 즉시 1회 상태 갱신
  • 잔여 시간 커스텀 팝오버: 상태 갱신과 동일하며 hover/focus 중에도 내용과 위치를 계속 갱신
  • dmesg: 진단 패널에서 열었을 때만 1초마다 갱신, 닫으면 중지
  • 주변 WiFi/Bluetooth 스캔: 상태 snapshot에 포함되지만 파일 캐시를 사용합니다. 캐시가 없거나 만료되어도 첫 응답은 실제 스캔을 기다리지 않고 DB의 마지막 성공 관측 목록을 즉시 반환하며, bin/control_scan_refresh.php를 백그라운드로 실행해 캐시를 갱신합니다. 화면의 마지막 관측 경과 문구는 1초 상태 렌더마다 현재 시각 기준으로 다시 계산합니다.
  • WiFi 관측 보정 timer: 10초마다 bin/wifi_observe.php 실행
  • WebSocket 소스 변경 감지: 15초마다 확인 후 재시작
  • 팬 슬라이더 자동 적용 debounce: 약 450ms

HA 알림 구조

Control은 서버에서 Home Assistant webhook으로 알림 payload를 보내고, HA 자동화가 notify.mobile_app_seocaegeonyi_z_fold7 서비스로 Android 알림을 전달합니다.

  • webhook id: /home/seo/secret/control.phpha_notify.webhook_id
  • 기본 발송 순서: 서울 HA https://ha.seoul.chaegeon.com 우선, 실패 시 목포 HA https://ha.chaegeon.com
  • payload 형식: title, message, data
  • Android 알림 속성은 data에 넣어 HA mobile app으로 전달
  • 사용 속성: tag, group, channel, importance, priority, ttl, sticky, persistent, renotify, color, notification_icon, vibrationPattern, ledColor, visibility, timeout, clickAction, url, actions
  • 발송 결과는 ha_notify_logs에 저장

배터리 SOC 낮음 경고는 control-battery-low tag를 사용합니다. SOC 값이 조금씩 달라져도 Android 알림은 같은 tag로 갱신되어 알림 목록에 여러 장이 계속 쌓이지 않습니다. 주의/경고/위험/긴급/복구 기준과 알림 쿨다운은 설정 모달의 알림 정책에서 조정합니다.

System Notice는 control-system-notice tag를 사용하며, 설정된 쿨다운으로 급격한 팬/온도 변화 알림을 제한합니다. alert 상태는 신규 진입과 유지 상태를 분리해 관리하며, alert 유지 중 반복 기록 여부와 기록 간격도 설정 모달에서 조정합니다.

시스템 알림 조건

배터리 알림과 System Notice는 서로 다른 조건으로 동작합니다.

  • 배터리 알림: 현재 배터리 SOC가 설정값 이하로 내려가면 발송합니다. 기본 단계는 20% 이하 주의, 15% 이하 경고, 10% 이하 위험, 5% 이하 긴급입니다. 같은 control-battery-low tag를 계속 갱신하므로 SOC 값이 1%씩 바뀌어도 알림 카드가 계속 쌓이지 않습니다. SOC가 복구 기준을 넘으면 같은 tag를 clear_notification으로 제거합니다.
  • System Notice 시작: 팬 모드가 off가 아닐 때, 최근 안정 기준선 대비 팬 RPM 차이가 팬 RPM 이상 감지 차이 이상이거나 온도 차이가 온도 이상 감지 차이 이상이면 alert로 진입합니다. RPM 감시는 현재 PWM 또는 기준 PWM이 RPM 감시 시작 PWM 이상일 때만 의미 있는 변화로 봅니다.
  • System Notice 기준선: normal 상태에서는 최근 sensor history에서 최신 몇 개 표본을 제외하고 절사 평균으로 기준선을 계속 갱신합니다. alert로 들어가면 진입 당시 온도/RPM/PWM 기준선을 고정해, alert 중 기준선이 문제 상태를 따라가며 사라지는 일을 막습니다.
  • System Notice 복구: 고정 기준선 또는 최신 rolling 기준선 대비 RPM/온도 차이가 각각 복구 차이 이하로 내려가면 normal로 돌아갑니다. 팬 모드가 off이면 fan off 기준선을 별도로 고정하고 System Notice alert를 만들지 않습니다.
  • System Notice 기록/발송: alert 신규 진입 시 system_notice_logs에 기록합니다. Alert 유지 중 반복 기록을 켜면 유지 중에도 설정한 반복 기록 간격마다 다시 기록할 수 있습니다. HA 알림은 시스템 유의사항 알림 쿨다운을 통과할 때만 발송합니다.
  • 저전압/스로틀링 표시: vcgencmd get_throttled의 현재 비트와 부팅 후 이력 비트를 읽어 현재 상태, 최근 감지, 지속시간, 최근 10분 통계를 표시합니다. 이 값은 System Notice와 별개이며, 팬/온도 alert 조건에는 직접 섞지 않습니다.

Android 알림 채널은 생성 후 휴대폰 설정에 의해 중요도와 진동 패턴이 고정될 수 있습니다. 채널을 다시 만들 필요가 있을 때는 운영 CLI로 제거 명령을 보낸 뒤 다음 알림에서 재생성합니다.

/usr/bin/php /var/www/control/bin/ha_notify_channel.php --remove=control_battery

WiFi 관측 자동화

5G 외부 WiFi 모듈은 iw station dumpconnected 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

이 작업은 팬 정책, 차트 로그 저장, HA 알림 발송과 분리되어 있으며 WiFi client 목록 확인과 wifi_observed_sessions 갱신만 수행합니다.

호스트명은 dnsmasq lease 값을 우선 사용합니다. lease hostname이 N/A이면 MAC 주소 끝 6자리 기반 임시명 기기-XXXXXX을 표시하고, 표의 호스트명을 클릭해 저장한 수동 이름은 wifi_client_aliases에 보관해 이후에도 우선 표시합니다.

신호/송신 속도/수신 속도는 실제 iw station dump 값이 있으면 그대로 사용합니다. 값이 비어 있으면 2.4G는 일반 802.11n 계열 링크 속도와 dBm 범위를 기준으로, 5G는 Realtek RTL8822BU/802.11ac 계열 동글의 최대 1300Mbps 특성을 참고해 dBm과 링크 속도 사이를 보정합니다. 화면의 신호 칸은 숫자 대신 최상, 매우 좋음, 좋음, 양호, 보통, 약함, 매우 약함 7단계 등급으로 표시하고, 마우스 hover/키보드 focus/모바일 touch 시 커스텀 툴팁으로 실제 dBm을 표시합니다. 툴팁 내용과 위치는 1초 상태 렌더마다 다시 갱신됩니다.

주변 WiFi 스캔은 대시보드 snapshot에 포함되지만 기본 60초 파일 캐시를 사용합니다. iw dev wlan0 scan, iw dev wlan1 scan을 시도하고 SSID, BSSID, 주파수, 채널, 신호, 보안, 마지막 관측, WiFi 세대, 채널 폭, 지원 속도, capability를 표시합니다. 캐시가 없거나 만료된 첫 요청에서는 실제 스캔을 기다리지 않고 DB의 마지막 성공 관측값을 즉시 반환한 뒤, 백그라운드 CLI가 캐시를 갱신합니다. 화면은 WiFi client 섹션 아래 별도 카드로 분리하며, 2.4G와 5G를 나란히 두지 않고 각각 독립 표로 세로 배치합니다.

스캔에 성공한 AP는 wifi_scan_observations에 BSSID 기준으로 저장합니다. SSID, BSSID, 신호, 보안, 채널 폭 같은 화면 표시값뿐 아니라 iw 원문과 파싱 결과 JSON도 함께 저장해 이후 분석 근거를 남깁니다. 다음 스캔이 실패하거나 특정 대역이 AP 모드라 스캔할 수 없어도 목록을 비우지 않고, DB에 누적된 BSSID별 마지막 성공 관측값을 계속 표시합니다. 화면의 마지막 관측 경과는 캐시된 API 값에 고정하지 않고 1초 상태 렌더마다 현재 시각 기준으로 다시 계산합니다. live 스캔 결과와 DB 관측값 모두 마지막 관측 시각과 경과 시간을 함께 표시합니다.

스캔 실패 사유는 API payload에는 남지만 대시보드에는 노란 경고 박스로 표시하지 않습니다. 스캔 표의 보안, 속도, capability 같은 긴 문자열은 말줄임으로 자르지 않고 표 전체를 가로 스크롤해 확인합니다. 대역 제목 옆 숫자는 해당 대역에 표시 중인 AP 개수입니다. 스캔 표에서 가로 스크롤을 드래그하거나 스크롤 중일 때는 1초 상태 렌더가 표 컨테이너를 교체하지 않도록 짧게 보류해 스크롤바 클릭/드래그가 끊기지 않게 합니다.

주변 Bluetooth 스캔은 WiFi 스캔 카드 아래 별도 카드로 표시합니다. bluetoothctl scan on, bluetoothctl devices, bluetoothctl info <address>를 조합해 주소, 이름, alias, RSSI, TxPower, 연결/페어링/신뢰/차단 상태, class, icon, UUID, 제조사 데이터, 서비스 데이터, modalias, appearance를 가능한 범위에서 수집합니다. 성공한 장치는 bluetooth_scan_observations에 주소 기준으로 저장하며 원문과 파싱 결과 JSON도 함께 남깁니다. Bluetooth 실시간 스캔은 10초 이상 걸릴 수 있으므로 화면 첫 응답에서는 기다리지 않고 마지막 관측값을 먼저 표시하며, 백그라운드 CLI가 새 캐시를 채웁니다. 스캔이 실패하거나 주변 장치가 일시적으로 사라져도 마지막 성공 관측값을 계속 표시하고, 마지막 관측 경과는 1초 상태 렌더마다 다시 계산합니다.

처리 흐름

  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. WiFi hostname이 비어 있으면 MAC 기반 임시명을 표시하고, 수동 저장한 MAC별 이름은 DB에서 우선 적용합니다.
  7. WiFi 신호/송신 속도/수신 속도 값이 비어 있으면 실제로 존재하는 다른 무선 지표와 대역별 기준으로 값을 채웁니다.
  8. 주변 WiFi 스캔은 캐시 주기를 확인한 뒤 iw dev <iface> scan을 시도하고, 성공 결과를 DB에 저장한 뒤 live 값과 마지막 관측값을 주변 WiFi 스캔 전용 카드에 표시합니다.
  9. 주변 Bluetooth 스캔은 캐시 주기를 확인한 뒤 bluetoothctl로 scan/devices/info를 시도하고, 성공 결과를 DB에 저장한 뒤 live 값과 마지막 관측값을 주변 Bluetooth 스캔 전용 카드에 표시합니다.
  10. 스캔 캐시가 만료된 경우 status snapshot은 스캔 완료를 기다리지 않고 refresh_pending=true와 마지막 관측값을 반환하며, 백그라운드 refresh가 다음 렌더에서 사용할 캐시를 생성합니다.
  11. control-wifi-observe.timer는 사용자 접속과 무관하게 10초마다 5G 관측 세션을 갱신합니다.
  12. WakeLock 버튼은 활성 상태를 초록색 버튼으로 표시합니다.
  13. Reboot 버튼은 설정된 확인 문구와 관리자 암호 재입력을 모두 통과한 뒤 서버 API로 재부팅을 요청합니다.
  14. 시스템 상태는 vcgencmd get_throttled를 읽어 현재 저전압/스로틀링 여부와 부팅 후 이력, 최근 감지 시각, 현재/최근 지속시간을 표시합니다.
  15. 저전압/스로틀링 상태 파일에는 최근 episode 시작/종료를 보관하고, 최근 10분 발생 횟수, 감지 누적시간, 감지 비율을 계산합니다.
  16. 배터리 잔여 시간은 최근 24시간 SOC 방전 추세와 최대 45일 장기 학습 프로파일을 함께 평가하고, 현재 CPU 전력 변화와 현실 상한을 반영합니다.
  17. 잔여 시간 차트의 마지막 지점은 현재 snapshot의 잔여 시간과 같은 값을 사용하도록 history 응답을 동기화합니다.
  18. 배터리 SOC 또는 System Notice 조건이 맞으면 Control이 HA webhook으로 알림을 발송하고 ha_notify_logs에 결과를 저장합니다.
  19. 대시보드는 최근 HA 알림 성공/실패 이력을 ha_notify_logs에서 읽어 진단 접힘 영역에 표시합니다.
  20. 대시보드 첫 화면에는 팬/전원/스로틀링/WiFi/핵심 차트를 우선 배치하고, 상세 차트, 프로세스 후보, 사용자 서비스, HA 알림 이력, dmesg는 접힘 영역으로 분리합니다.

주요 함수/모듈

  • collect_snapshot(): 센서와 fan 상태 snapshot 생성
  • custom_systemd_services(): /etc/systemd/system/*.service 단위를 조회해 사용자 생성 서비스 상태와 최근 journal 로그를 구성
  • systemd_service_logs(): 서비스별 최근 journal 로그 조회
  • read_throttled_flags(): vcgencmd get_throttled를 직접 조회하고, 권한 문제로 실패하면 sudo 경유 조회를 시도
  • throttled_event_status(): 저전압/스로틀링 episode 이력, 복구 중/반복 발생 판정, 최근 10분 통계를 계산
  • throttled_statuses(): 라즈베리파이 throttled flag를 읽고 저전압/스로틀링 감지 이력을 상태 파일로 추적
  • apply_fan_policy(): 팬 목표값 계산과 적용
  • json_out(): API JSON 응답 표준화
  • apply_observed_wifi_connected_time(): 5G WiFi 연결 시간이 없는 client에 서버 관측 경과 시간 적용
  • wifi_client_aliases(), save_wifi_client_alias(): MAC별 WiFi 호스트명 조회와 저장
  • apply_wifi_estimates(): WiFi 신호/송신 속도/수신 속도 결측값 보정
  • wifi_scan_data(), parse_wifi_scan_networks(): 주변 WiFi AP 스캔, 실패 사유 정리, 파일 캐시 구성
  • save_wifi_scan_observations(), wifi_scan_observed_networks(), merge_wifi_scan_networks(): 주변 WiFi 마지막 성공 관측값 저장, 조회, live/DB 결과 병합
  • bluetooth_scan_data(), parse_bluetooth_info(): 주변 Bluetooth 장치 스캔, 세부 정보 수집, 파일 캐시 구성
  • save_bluetooth_scan_observations(), bluetooth_scan_observed_devices(), merge_bluetooth_scan_devices(): 주변 Bluetooth 마지막 성공 관측값 저장, 조회, live/DB 결과 병합
  • wifi_scan_refresh_payload(), bluetooth_scan_refresh_payload(), scan_start_background_refresh(): 느린 실제 스캔을 백그라운드 캐시 갱신으로 분리
  • send_ha_notify(): HA webhook 알림 발송, 서울 우선/목포 fallback, 성공/실패 로그 저장
  • ha_notify_due(): 동일 tag 기준 쿨다운 판정
  • clear_ha_notification(): 같은 tag의 Android 알림 제거 명령 발송
  • battery_low_notify_profile(): 배터리 SOC 단계별 채널, 중요도, 아이콘, 진동 정책 선택
  • send_battery_low_notify_if_needed(): 설정된 배터리 SOC 기준에 따라 낮음/복구 알림 발송
  • ha_notify_log_rows(): 대시보드 HA 알림 이력 조회
  • battery_trend_history(): 최근 24시간 배터리 기록을 1분 단위로 집계
  • battery_profile_history(): 최대 45일 배터리 기록을 약 5분 간격으로 샘플링하고 파일 캐시로 보관
  • weighted_linear_regression(), numeric_mad(): SOC 기울기 계산과 회귀 잔차 튐 제거
  • battery_regression_trend_candidate(): 1분 SOC 집계를 강건 회귀로 분석해 정밀 방전 속도 후보 계산
  • battery_voltage_trend_candidate(): 전압 기울기와 동적 하한 전압으로 잔여 시간 후보 계산
  • battery_energy_profile_candidate(): 장기 기록에서 SOC 1%당 실제 사용 Wh를 학습해 잔여 시간 후보 계산
  • battery_capacity_power_candidate(): 설정 배터리 용량과 최근 전력으로 용량 기반 후보 계산
  • battery_runtime_cap_seconds(), battery_cap_candidate(), battery_cap_result(): SOC 비례 현실 상한과 상한 초과 후보 감쇠 적용
  • refine_battery_candidates(): 후보 방전 속도의 중앙값에서 크게 벗어난 값을 제외하거나 가중치 축소
  • battery_learned_profile_candidate(): 누적 기록에서 SOC/부하 구간별 장기 방전 프로파일 후보 계산
  • battery_remaining_estimate(): SOC 다중 시간창 방전 추세, 장기 학습 프로파일, 현재 부하 보정으로 잔여 시간 계산
  • battery_power_fallback_estimate(): SOC 추세가 부족할 때 배터리 용량과 최근 CPU 전력 평균으로 잔여 시간 대체 계산
  • sync_current_battery_remaining_history(): 잔여 시간 차트 마지막 지점을 현재 snapshot 잔여 시간과 동기화
  • batteryRemainingTitle(), showBatteryTooltip(), updateBatteryTooltip(): 잔여 시간 계산 근거 커스텀 팝오버 표시와 실시간 갱신
  • setting_definitions(), settings_payload(), save_settings(), reset_settings(): 설정 모달 항목 정의, 조회, 저장, 초기화
  • bin/ha_notify_channel.php: Android 알림 채널 제거 명령 CLI
  • bin/wifi_observe.php: 화면 접속 없이 wifi_data()를 호출해 5G 관측 세션을 선제 갱신
  • 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, 로그인 세션, 설정된 확인 문구, 관리자 암호 재검증을 모두 요구합니다.
  • 로그인 화면은 CSRF 검사를 수행하고, 설정한 실패 횟수/집계 시간/잠금 시간에 따라 세션 단위 잠금을 적용합니다.
  • Remember login은 설정에 따라 Secure/SameSite, 토큰 길이, 만료 기간, User-Agent 고정, IPv4 접두사 고정을 조정할 수 있습니다.
  • WiFi restart/reload 허용 여부와 root 명령 timeout은 설정 모달에서 조정합니다.
  • 앱 비밀번호와 HA 알림 설정은 저장소 밖 secret 파일로 관리합니다.
  • HA webhook id는 /home/seo/secret/control.phpha_notify.webhook_id에 둡니다. 값이 없으면 발송하지 않고 ha_notify_logs에 실패로 기록합니다.

운영 체크포인트

  • 센서 수집 주기와 DB 증가량을 확인합니다.
  • 배터리팩 변경 시 /home/seo/secret/control.phpbattery 값을 먼저 갱신합니다. 현재 기준은 2200mAh x 8 parallel = 17600mAh, nominal 65.12Wh이며, SOC 추세가 부족한 경우의 fallback 계산에 사용됩니다.
  • 잔여 시간이 갑자기 흔들리면 커스텀 팝오버의 계산 출처, 반영 구간, 시간당 SOC 감소, 추세 신뢰도, 최근 CPU 전력을 먼저 확인합니다.
  • WiFi client 표와 주변 WiFi/Bluetooth 스캔 표는 갱신 중에도 가로 스크롤 위치를 보존합니다. 주변 WiFi 스캔은 별도 카드에서 대역별 독립 표로 렌더링하고, Bluetooth 스캔은 그 아래 독립 카드에서 장치별 상세 표로 렌더링해 client 표 스크롤과 서로 간섭하지 않도록 합니다. 스캔 표는 스크롤/드래그 중 DOM 재생성을 잠시 보류해 사용자의 좌우 스크롤 조작을 우선합니다.
  • 하드웨어 또는 OS 변경 후 fan sysfs 경로를 확인합니다.
  • 2.4G 내장 WiFi는 hostapd-24g.service 개별 restart 직후 일부 IoT 단말이 WPA/EAPOL 재협상 루프에 들어갈 수 있으므로, 안정화된 상태에서는 개별 restart를 피하고 필요 시 전체 reboot 또는 채널 변경으로 재초기화합니다.
  • 5G WiFi 연결 시간은 외부 모듈이 값을 제공하지 않을 때 서버가 처음 감지한 시각 기준으로 계산합니다. 현재 5G 목록에서 MAC이 사라지면 관측 세션을 즉시 종료하므로 재연결 시 0부터 다시 누적됩니다.
  • control-wifi-observe.timer는 10초마다 /var/www/control/bin/wifi_observe.php를 실행해 사용자가 대시보드에 접속하지 않아도 5G 연결 시간 카운터를 시작하고 유지합니다.
  • WebSocket은 장기 실행 프로세스이므로 public/api.php, config/config.php, bin/control_ws.php 변경을 감지하면 15초 안에 종료되고 control-websocket.service가 새 프로세스로 재시작합니다.
  • WebSocket 장기 실행 중 DB 연결이 끊길 수 있으므로 reconnect 로그를 확인합니다.
  • HA 알림 자동화는 서울 /home/seo/homeassistant/automations.yaml, 목포 /mnt/synology-docker/homeassistant/config/automations.yaml 양쪽의 CONTROL_HA_NOTIFY_WEBHOOK 계열 항목에서 관리합니다.
  • SmartThings 재로드 자동화는 서울 전력분전반과 목포 누전차단기 전력 센서의 값이 10초 이상 변하지 않으면 통합을 재로드합니다.
  • HA 알림 채널의 중요도나 진동이 예상과 다르면 휴대폰 알림 채널 설정을 확인하거나 bin/ha_notify_channel.php로 해당 채널 제거 명령을 보냅니다.
  • Reboot API 사용 전 웹 서버 실행 계정의 sudoers에 /usr/sbin/reboot 비밀번호 없는 실행 권한이 제한적으로 설정되어 있는지 확인합니다.
  • 저전압/스로틀링이 N/A로 보이면 웹 서버 실행 계정의 /dev/vcio 접근 권한과 /usr/bin/vcgencmd get_throttled sudoers 허용 여부를 확인합니다.
  • 언어/테마 표시가 예상과 다르면 브라우저 localStorage의 controlLang, controlTheme 값을 확인합니다.
  • /mnt/synology-web는 원격 Synology NFS 마운트이며 sec=sys 숫자 UID/GID 매핑을 사용합니다. 로컬 seo의 기본 UID/GID만으로는 /mnt/synology-web/log, /mnt/synology-web/report, /mnt/synology-web/config에 쓸 수 없고, synologyweb primary group으로 실행해야 합니다. /home/seo/log.sh는 시작 시 sg synologyweb으로 재실행하도록 구성합니다.
  • /mnt/synology-docker/homeassistant/config는 현재 일반 seosynologyweb primary group 모두 쓰기 가능합니다.