diff --git a/README.md b/README.md index 7e15429..66ba7dc 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ cd /var/www/control /usr/bin/python3 touch_panel/panel.py ``` -패널은 Control PHP/API/DB snapshot을 호출하지 않는 standalone 방식입니다. Python이 `/sys/class/net`, `/proc/diskstats`, `/sys/class/thermal`, 팬 hwmon 파일을 직접 읽습니다. 상태패널의 네트워크/I/O 표시와 UPLINK 트래픽 그래프는 100ms 간격으로 갱신하고, 팬 카드와 온도/RPM 그래프는 500ms 간격으로 갱신합니다. 왼쪽 상태패널은 `RASPBERRY`, `SYNOLOGY`, `CHECK` 3열이며, 라즈베리파이 순서는 `SSD I/O`, `LOCAL`, `UPLINK`, `ETH1`, `5G`, `2.4G`입니다. 시놀로지 열은 `PING`, 정상 디스크 `SATA1 I/O`, `SATA2 I/O`, `SATA4 I/O`, `LOCAL`, `UPLINK`를 표시합니다. `CHECK` 열은 `RPI TEMP`, `RPI FAN`, `RPI THROT`, `SYNO TEMP`, `SYNO FAN`, `SYNO GUARD`를 표시합니다. `RPI TEMP`와 `SYNO TEMP`는 80도 이상부터 빨간 표시등을 500ms 간격으로 점멸하고, 팬 카드는 RPM 0 또는 7000 초과를 빨강, 4500 초과를 주황으로 표시합니다. 시놀로지 `PING`은 장기 실행 `ping -i 0.5` 출력 파싱으로 읽고, 15ms 이하 초록, 30ms 이하 주황, 50ms 이하 빨강, 50ms 초과 빨강 500ms 점멸입니다. `RPI THROT`은 정상 초록, 부팅 후 이력 주황, 현재 언더볼트 빨강, 현재 스로틀 빨강 500ms 점멸, 현재 언더볼트와 스로틀 동시 발생 빨강 100ms 점멸로 표시합니다. `SYNO GUARD`는 `synology-fan-guard.service`가 active이고 DSM 자동제어면 초록 `AUTO`, 고온으로 PWM 최대 강제 중이면 주황 `FULL`, 서비스 실패나 팬 RPM 0이면 빨강 `FAIL`로 표시합니다. `LOCAL`은 `lo`, `UPLINK`는 기본 라우트 인터페이스 기준입니다. 라즈베리파이 `ETH1`은 컴퓨터와 연결된 물리 포트 단독 카운터이며 `br0`는 합산하지 않습니다. 시놀로지는 SSH를 유지한 채 100ms마다 디스크별 sector I/O, `LOCAL`, `UPLINK` 카운터를 스트림으로 가져오고, 각 SATA 카드 값에는 SMART 온도를 표시합니다. 라즈베리파이 CPU/SSD 온도, 시놀로지 CPU 온도/FAN RPM, 시놀로지 SATA SMART 온도는 500ms 간격으로 캐시해 표시합니다. 왼쪽 표시등은 장식용 heartbeat가 아니라 직전 샘플 대비 현재 카운터 증가량이 0 초과인지에 반응합니다. 각 타일에는 글자 아래에 세 개의 작은 표시등을 두며 읽기/업로드은 초록, 쓰기/다운로드는 주황, 오류/드롭 증가는 빨강으로 표시합니다. 활동 표시등은 이벤트 감지 후 120ms 동안 유지하고, 오류 표시등은 2초 동안 유지해 너무 짧은 이벤트도 눈에 보이게 합니다. 네트워크 카드 오른쪽 값은 항상 `D n`으로 드롭 누적을 표시합니다. `SSD I/O`의 값은 `/run/control-rpi-disk-temp` 캐시 파일의 `/dev/sda` Samsung T7 디스크 자체 온도입니다. 캐시 파일은 root로 실행되는 `control-rpi-disk-temp.service`가 몇 초마다 `smartctl -d sntasmedia`로 갱신하므로 GUI 패널은 sudo를 반복 실행하지 않습니다. 네트워크 상태값은 정상, 드롭 누적, 꺼짐으로 표시합니다. 상태패널 최하단의 `PC` 타일은 `192.168.50.73`에 1초마다 ping을 보내 켜짐 여부를 표시하고, 꺼져 있을 때 누르면 `74:56:3C:CD:CD:79`로 WOL magic packet을 보냅니다. PC 타일의 주황은 WOL 후 최대 1분 동안 컴퓨터 응답 대기 표시, 빨강은 magic packet 전송 실패 시 5초 또는 1분 대기 후 응답 실패 시 1분 표시입니다. +패널은 Control PHP/API/DB snapshot을 호출하지 않는 standalone 방식입니다. Python이 `/sys/class/net`, `/proc/diskstats`, `/sys/class/thermal`, 팬 hwmon 파일을 직접 읽습니다. 상태패널의 네트워크/I/O 표시와 UPLINK 트래픽 그래프는 100ms 간격으로 갱신하고, 팬 카드와 온도/RPM 그래프는 500ms 간격으로 갱신합니다. 왼쪽 상태패널은 `RASPBERRY`, `SYNOLOGY`, `CHECK` 3열이며, 라즈베리파이 순서는 `SSD I/O`, `LOCAL`, `UPLINK`, `ETH1`, `5G`, `2.4G`입니다. 시놀로지 열은 `PING`, 정상 디스크 `SATA1 I/O`, `SATA2 I/O`, `SATA4 I/O`, `LOCAL`, `UPLINK`를 표시합니다. `CHECK` 열은 `RPI TEMP`, `RPI FAN`, `RPI THROT`, `SYNO TEMP`, `SYNO FAN`, `SYNO GUARD`를 표시합니다. `RPI TEMP`와 `SYNO TEMP`는 80도 이상부터 빨간 표시등을 500ms 간격으로 점멸하고, 팬 카드는 RPM 0 또는 7000 초과를 빨강, 4500 초과를 주황으로 표시합니다. 시놀로지 `PING`은 장기 실행 `ping -i 0.5` 출력 파싱으로 읽고, 15ms 이하 초록, 30ms 이하 주황, 50ms 이하 빨강, 50ms 초과 빨강 500ms 점멸입니다. `RPI THROT`은 정상 초록, 부팅 후 이력 주황, 현재 언더볼트 빨강, 현재 스로틀 빨강 500ms 점멸, 현재 언더볼트와 스로틀 동시 발생 빨강 100ms 점멸로 표시합니다. `SYNO GUARD`는 `synology-fan-guard.service`가 active이고 DSM 자동제어면 초록 `AUTO`, 고온으로 PWM 최대 강제 중이면 주황 `FULL`, 서비스 실패나 팬 RPM 0이면 빨강 `FAIL`로 표시합니다. `LOCAL`은 `lo`, `UPLINK`는 기본 라우트 인터페이스 기준입니다. 라즈베리파이 `ETH1`은 컴퓨터와 연결된 물리 포트 단독 카운터이며 `br0`는 합산하지 않습니다. 시놀로지는 SSH를 유지한 채 100ms마다 디스크별 sector I/O, `LOCAL`, `UPLINK` 카운터만 스트림으로 가져옵니다. SATA SMART/온도 직접 조회는 응답 지연 위험 때문에 대시보드에서 실행하지 않고, 각 SATA 카드에는 `조회 중지`를 표시합니다. 라즈베리파이 CPU/SSD 온도와 시놀로지 CPU 온도/FAN RPM은 500ms 간격으로 캐시해 표시합니다. 왼쪽 표시등은 장식용 heartbeat가 아니라 직전 샘플 대비 현재 카운터 증가량이 0 초과인지에 반응합니다. 각 타일에는 글자 아래에 세 개의 작은 표시등을 두며 읽기/업로드은 초록, 쓰기/다운로드는 주황, 오류/드롭 증가는 빨강으로 표시합니다. 활동 표시등은 이벤트 감지 후 120ms 동안 유지하고, 오류 표시등은 2초 동안 유지해 너무 짧은 이벤트도 눈에 보이게 합니다. 네트워크 카드 오른쪽 값은 항상 `D n`으로 드롭 누적을 표시합니다. `SSD I/O`의 값은 `/run/control-rpi-disk-temp` 캐시 파일의 `/dev/sda` Samsung T7 디스크 자체 온도입니다. 캐시 파일은 root로 실행되는 `control-rpi-disk-temp.service`가 몇 초마다 `smartctl -d sntasmedia`로 갱신하므로 GUI 패널은 sudo를 반복 실행하지 않습니다. 네트워크 상태값은 정상, 드롭 누적, 꺼짐으로 표시합니다. 상태패널 최하단의 `PC` 타일은 `192.168.50.73`에 1초마다 ping을 보내 켜짐 여부를 표시하고, 꺼져 있을 때 누르면 `74:56:3C:CD:CD:79`로 WOL magic packet을 보냅니다. PC 타일의 주황은 WOL 후 최대 1분 동안 컴퓨터 응답 대기 표시, 빨강은 magic packet 전송 실패 시 5초 또는 1분 대기 후 응답 실패 시 1분 표시입니다. 오른쪽 `기록` 영역은 라즈베리파이 온도/RPM, 라즈베리파이 UPLINK 트래픽, 시놀로지 온도/RPM, 시놀로지 UPLINK 트래픽 그래프를 순서대로 표시합니다. 온도/RPM 그래프는 최근 60초 기준 500ms 샘플을 사용하고, UPLINK 트래픽 그래프는 100ms 샘플과 200포인트 기준으로 표시합니다. 트래픽 속도와 그래프 Y축은 byte/sec 기준으로 `B/s`, `KB/s`, `MB/s`, `GB/s` 중 자동 변환하고, 다운로드 주황/업로드 초록 두 선을 같은 축 범위에 맞춰 그립니다. 그래프 우측 상단 현재값은 고정폭으로 표시합니다. diff --git a/touch_panel/panel.py b/touch_panel/panel.py index a2d89ea..6a101dd 100755 --- a/touch_panel/panel.py +++ b/touch_panel/panel.py @@ -70,26 +70,6 @@ while :; do sleep 0.1 done """ -SYNOLOGY_DISK_TEMP_SCRIPT = r""" -for disk in /dev/sata1 /dev/sata2 /dev/sata4; do - temp="-" - if [ -b "$disk" ]; then - temp="$( - /usr/bin/timeout 3 sudo -n /usr/bin/smartctl -A "$disk" 2>/dev/null | - awk '/Temperature_Celsius|Airflow_Temperature_Cel/ {print $10; exit}' - )" - case "$temp" in - ''|*[!0-9]*) temp="-" ;; - esac - fi - printf '%s=%s\n' "${disk##/dev/}" "$temp" -done | -awk -F= ' - $1 == "sata1" {printf "SATA1_TEMP=%s\n", $2 ? $2 : "-"} - $1 == "sata2" {printf "SATA2_TEMP=%s\n", $2 ? $2 : "-"} - $1 == "sata4" {printf "SATA4_TEMP=%s\n", $2 ? $2 : "-"} -' -""" SYNOLOGY_HEALTH_SCRIPT = r""" cpu="$(cat /sys/class/hwmon/hwmon0/temp1_input 2>/dev/null)" fan="$(cat /sys/class/hwmon/hwmon1/fan2_input 2>/dev/null)" @@ -116,7 +96,6 @@ DISK_TEMP_INTERVAL_MS = TEMP_INTERVAL_MS DISK_TEMP_CACHE_FILE = "/run/control-rpi-disk-temp" DISK_TEMP_CACHE_MAX_AGE_SECONDS = 30 RPI_THROTTLE_INTERVAL_MS = SAMPLE_INTERVAL_MS -SYNOLOGY_DISK_TEMP_INTERVAL_MS = TEMP_INTERVAL_MS SYNOLOGY_HEALTH_INTERVAL_MS = FAN_INTERVAL_MS @@ -588,7 +567,6 @@ class TouchPanel(QMainWindow): self.last_synology_net_rate = {} self.synology_buffer = "" self.synology_values = {} - self.synology_disk_temps = {} self.synology_cpu_temp = 0.0 self.synology_fan_rpm = 0 self.last_io = None @@ -642,12 +620,6 @@ class TouchPanel(QMainWindow): self.synology_timer.timeout.connect(self.update_synology_status) self.synology_timer.start(SYNOLOGY_RESTART_INTERVAL_MS) - self.synology_disk_temp_proc = QProcess(self) - self.synology_disk_temp_proc.finished.connect(self.synology_disk_temp_finished) - self.synology_disk_temp_timer = QTimer(self) - self.synology_disk_temp_timer.timeout.connect(self.update_synology_disk_temp) - self.synology_disk_temp_timer.start(SYNOLOGY_DISK_TEMP_INTERVAL_MS) - self.synology_health_proc = QProcess(self) self.synology_health_proc.finished.connect(self.synology_health_finished) self.synology_health_timer = QTimer(self) @@ -677,7 +649,6 @@ class TouchPanel(QMainWindow): self.ping_pc() self.update_disk_temp() self.update_synology_status() - self.update_synology_disk_temp() self.update_synology_health() self.ping_synology() self.update_rpi_throttle() @@ -943,29 +914,11 @@ class TouchPanel(QMainWindow): self.synology_values = {} self.synology_proc.start(SYNOLOGY_SSH[0], SYNOLOGY_SSH[1:] + [SYNOLOGY_STATUS_SCRIPT]) - def update_synology_disk_temp(self): - if self.synology_disk_temp_proc.state() != QProcess.NotRunning: - return - self.synology_disk_temp_proc.start(SYNOLOGY_SSH[0], SYNOLOGY_SSH[1:] + [SYNOLOGY_DISK_TEMP_SCRIPT]) - def update_synology_health(self): if self.synology_health_proc.state() != QProcess.NotRunning: return self.synology_health_proc.start(SYNOLOGY_SSH[0], SYNOLOGY_SSH[1:] + [SYNOLOGY_HEALTH_SCRIPT]) - def synology_disk_temp_finished(self, exit_code, exit_status): - del exit_status - if exit_code != 0: - return - output = bytes(self.synology_disk_temp_proc.readAllStandardOutput()).decode("utf-8", "ignore").strip() - temps = {} - for line in output.splitlines(): - if "=" not in line: - continue - key, value = line.split("=", 1) - temps[key.strip()] = value.strip() - self.synology_disk_temps = temps - def synology_health_finished(self, exit_code, exit_status): del exit_status if exit_code != 0: @@ -1097,10 +1050,8 @@ class TouchPanel(QMainWindow): ) def synology_disk_temp_detail(self, disk): - value = self.synology_disk_temps.get(f"{disk}_TEMP", "") - if not value or value == "-" or value == "0": - return "" - return f"{value} C" + del disk + return "조회 중지" def set_synology_net_tile(self, key, rx, tx, errors, drops, detail): previous = self.last_synology.get(key)