터치 패널 PC 버튼 확장 복원
This commit is contained in:
1 parent
637739386e
commit
64ded5e566
2 files changed
+117
-6
No files matched your search
@@ -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 파일을 직접 읽습니다. 화면은 왼쪽 `RASPBERRY` 18칸, 오른쪽 `SYNOLOGY` 18칸 카드로 나눕니다. 왼쪽 하단의 1칸짜리 영역은 아직 기능을 두지 않고 빈칸으로 둡니다.
|
||||
패널은 Control PHP/API/DB snapshot을 호출하지 않는 standalone 방식입니다. Python이 `/sys/class/net`, `/proc/diskstats`, `/sys/class/thermal`, 팬 hwmon 파일을 직접 읽습니다. 화면은 왼쪽 `RASPBERRY` 18칸, 오른쪽 `SYNOLOGY` 18칸 카드로 나눕니다. 하단에는 좌우 6칸 폭을 한 번에 쓰는 긴 `PC` 버튼을 둡니다.
|
||||
|
||||
`RASPBERRY` 카드는 `SSD I/O`, `TEMP`, `FAN`, `THROT`, `LOAD`, `MEM`, `ROOT`, `UPTIME`, `LOCAL`, `UPLINK`, `ETH1`, `5G`, `2.4G`, `DOCKER`, `NGINX`, `MARIADB`, `CONTROL`, `FANPOL`을 표시합니다. 네트워크와 SSD I/O 점멸은 100ms 샘플에서 직전 카운터보다 증가했는지를 보고, CPU 온도/SSD 온도와 팬 RPM은 500ms 간격으로 표시합니다. systemd 서비스 상태는 5초마다 `systemctl is-active`로 확인합니다.
|
||||
|
||||
@@ -165,6 +165,8 @@ cd /var/www/control
|
||||
|
||||
각 타일에는 글자 아래에 세 개의 작은 표시등을 두며 읽기/업로드는 초록, 쓰기/다운로드는 주황, 오류/드롭 증가는 빨강으로 표시합니다. 활동 표시등은 이벤트 감지 후 120ms 동안 유지하고, 오류 표시등은 2초 동안 유지해 너무 짧은 이벤트도 눈에 보이게 합니다. 네트워크 카드 오른쪽 값은 항상 `D n`으로 드롭 누적을 표시합니다. `LOCAL`은 `lo`, `UPLINK`는 기본 라우트 인터페이스 기준입니다. 라즈베리파이 `ETH1`은 컴퓨터와 연결된 물리 포트 단독 카운터이며 `br0`는 합산하지 않습니다. `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을 보냅니다. 주황은 WOL 후 최대 1분 동안 컴퓨터 응답 대기 표시, 빨강은 magic packet 전송 실패 시 5초 또는 1분 대기 후 응답 실패 시 1분 표시입니다.
|
||||
|
||||
GUI 자동 실행은 데스크톱 autostart가 1차 기준입니다.
|
||||
|
||||
```bash
|
||||
|
||||
+114
-5
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
import time
|
||||
import socket
|
||||
import os
|
||||
import shutil
|
||||
from collections import deque
|
||||
@@ -28,6 +29,8 @@ NET_GROUPS = {
|
||||
"wifi5": ["wlan1"],
|
||||
}
|
||||
|
||||
PC_IP = "192.168.50.73"
|
||||
PC_MAC = "74:56:3C:CD:CD:79"
|
||||
RPI_SERVICE_UNITS = [
|
||||
("rpi_docker", "docker.service"),
|
||||
("rpi_nginx", "nginx.service"),
|
||||
@@ -340,6 +343,21 @@ def disk_io():
|
||||
return reads, writes, not found
|
||||
|
||||
|
||||
def send_magic_packet(mac):
|
||||
clean = mac.replace(":", "").replace("-", "")
|
||||
if len(clean) != 12:
|
||||
return False
|
||||
packet = bytes.fromhex("ff" * 6 + clean * 16)
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
for target in ["192.168.50.255", "255.255.255.255"]:
|
||||
sock.sendto(packet, (target, 9))
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
class StatusDot(QWidget):
|
||||
def __init__(self, active_color):
|
||||
super().__init__()
|
||||
@@ -462,6 +480,72 @@ class StatusTile(QFrame):
|
||||
self.error_dot.set_steady(error_on)
|
||||
|
||||
|
||||
class PcWakeButton(StatusTile):
|
||||
def __init__(self):
|
||||
super().__init__("PC")
|
||||
self.online = False
|
||||
self.last_wake_at = 0.0
|
||||
self.wait_until = 0.0
|
||||
self.error_until = 0.0
|
||||
self.wait_phase = False
|
||||
self.blink_timer = QTimer(self)
|
||||
self.blink_timer.timeout.connect(self.tick_waiting)
|
||||
self.blink_timer.start(250)
|
||||
|
||||
def set_online(self, online):
|
||||
self.online = online
|
||||
self.read_dot.set_steady(online)
|
||||
now = time.monotonic()
|
||||
self.error_dot.set_steady(now < self.error_until)
|
||||
if online:
|
||||
self.wait_until = 0.0
|
||||
self.wait_phase = False
|
||||
self.write_dot.set_steady(False)
|
||||
self.detail.setText("켜짐")
|
||||
elif now < self.wait_until:
|
||||
remaining = int(self.wait_until - now)
|
||||
self.detail.setText(f"대기 {remaining}s")
|
||||
else:
|
||||
self.write_dot.set_steady(False)
|
||||
self.detail.setText("꺼짐")
|
||||
|
||||
def tick_waiting(self):
|
||||
now = time.monotonic()
|
||||
if not self.online and now < self.wait_until:
|
||||
self.wait_phase = not self.wait_phase
|
||||
self.write_dot.set_steady(self.wait_phase)
|
||||
if not self.online and self.wait_until > 0 and now >= self.wait_until:
|
||||
self.wait_until = 0.0
|
||||
self.wait_phase = False
|
||||
self.write_dot.set_steady(False)
|
||||
self.error_until = now + 60
|
||||
self.error_dot.set_steady(True)
|
||||
self.detail.setText("실패")
|
||||
if self.error_until > 0 and now >= self.error_until:
|
||||
self.error_until = 0.0
|
||||
self.error_dot.set_steady(False)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if event.button() == Qt.LeftButton:
|
||||
now = time.monotonic()
|
||||
if not self.online and now >= self.wait_until:
|
||||
self.last_wake_at = now
|
||||
self.wait_until = now + 60
|
||||
self.wait_phase = True
|
||||
self.write_dot.set_steady(True)
|
||||
self.error_until = 0.0
|
||||
self.error_dot.set_steady(False)
|
||||
self.detail.setText("깨우는 중")
|
||||
if not send_magic_packet(PC_MAC):
|
||||
self.wait_until = 0.0
|
||||
self.error_until = now + 5
|
||||
self.error_dot.set_steady(True)
|
||||
super().mousePressEvent(event)
|
||||
|
||||
def tick(self):
|
||||
self.error_dot.tick()
|
||||
|
||||
|
||||
class Sparkline(QFrame):
|
||||
def __init__(self, title, color="#38bdf8", second_color=None, y_unit="", right_unit="", auto_byte_unit=False, max_points=120):
|
||||
super().__init__()
|
||||
@@ -610,6 +694,12 @@ class TouchPanel(QMainWindow):
|
||||
self.fan_timer.timeout.connect(self.update_fan_status)
|
||||
self.fan_timer.start(FAN_INTERVAL_MS)
|
||||
|
||||
self.pc_ping = QProcess(self)
|
||||
self.pc_ping.finished.connect(self.pc_ping_finished)
|
||||
self.pc_timer = QTimer(self)
|
||||
self.pc_timer.timeout.connect(self.ping_pc)
|
||||
self.pc_timer.start(1000)
|
||||
|
||||
self.disk_temp_timer = QTimer(self)
|
||||
self.disk_temp_timer.timeout.connect(self.update_disk_temp)
|
||||
self.disk_temp_timer.start(DISK_TEMP_INTERVAL_MS)
|
||||
@@ -653,6 +743,7 @@ class TouchPanel(QMainWindow):
|
||||
self.pulse_timer.start(25)
|
||||
self.sample()
|
||||
self.update_fan_status()
|
||||
self.ping_pc()
|
||||
self.update_disk_temp()
|
||||
self.update_rpi_services()
|
||||
self.update_synology_status()
|
||||
@@ -663,9 +754,13 @@ class TouchPanel(QMainWindow):
|
||||
|
||||
def _build_ui(self):
|
||||
root = QWidget()
|
||||
layout = QHBoxLayout(root)
|
||||
layout = QVBoxLayout(root)
|
||||
layout.setContentsMargins(4, 8, 10, 8)
|
||||
layout.setSpacing(8)
|
||||
layout.setSpacing(6)
|
||||
|
||||
panel_layout = QHBoxLayout()
|
||||
panel_layout.setContentsMargins(0, 0, 0, 0)
|
||||
panel_layout.setSpacing(8)
|
||||
|
||||
rpi_rows = [
|
||||
(("io", "SSD I/O"), ("rpi_temp", "TEMP"), ("rpi_fan", "FAN")),
|
||||
@@ -684,7 +779,7 @@ class TouchPanel(QMainWindow):
|
||||
(("synology_sata1", "SATA1 I/O"), ("synology_sata2", "SATA2 I/O"), ("synology_sata4", "SATA4 I/O")),
|
||||
]
|
||||
|
||||
left = self.build_status_panel("RASPBERRY", rpi_rows, fixed_width=398, bottom_blank=True)
|
||||
left = self.build_status_panel("RASPBERRY", rpi_rows, fixed_width=398)
|
||||
right = self.build_status_panel("SYNOLOGY", synology_rows)
|
||||
|
||||
header = QHBoxLayout()
|
||||
@@ -697,8 +792,13 @@ class TouchPanel(QMainWindow):
|
||||
header.addWidget(self.clock)
|
||||
right.layout().insertLayout(1, header)
|
||||
|
||||
layout.addWidget(left)
|
||||
layout.addWidget(right, 1)
|
||||
panel_layout.addWidget(left)
|
||||
panel_layout.addWidget(right, 1)
|
||||
layout.addLayout(panel_layout, 1)
|
||||
|
||||
self.pc_button = PcWakeButton()
|
||||
self.pc_button.setFixedHeight(48)
|
||||
layout.addWidget(self.pc_button)
|
||||
self.setCentralWidget(root)
|
||||
|
||||
def build_status_panel(self, title_text, rows, fixed_width=None, bottom_blank=False):
|
||||
@@ -836,6 +936,15 @@ class TouchPanel(QMainWindow):
|
||||
detail = "OK" if active else state.upper()[:6]
|
||||
self.rows[key].set_state(detail, read_steady=active, write_steady=False, error_steady=not active)
|
||||
|
||||
def ping_pc(self):
|
||||
if self.pc_ping.state() != QProcess.NotRunning:
|
||||
return
|
||||
self.pc_ping.start("/usr/bin/ping", ["-c", "1", "-W", "1", PC_IP])
|
||||
|
||||
def pc_ping_finished(self, exit_code, exit_status):
|
||||
del exit_status
|
||||
self.pc_button.set_online(exit_code == 0)
|
||||
|
||||
def ping_synology(self):
|
||||
if self.synology_ping_proc.state() != QProcess.NotRunning:
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user