From a2a45a01185259be9bc6e9dd72c214749a1e2447 Mon Sep 17 00:00:00 2001 From: seo Date: Thu, 30 Jul 2026 21:54:20 +0900 Subject: [PATCH] =?UTF-8?q?=ED=84=B0=EC=B9=98=20=ED=8C=A8=EB=84=90=20WAN?= =?UTF-8?q?=20=EC=86=8D=EB=8F=84=20=EC=9E=90=EB=8F=99=20=EB=8B=A8=EC=9C=84?= =?UTF-8?q?=20=EB=B3=80=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- touch_panel/panel.py | 70 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index daf7cca..4323ba2 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,7 @@ cd /var/www/control 패널은 Control PHP/API/DB snapshot을 호출하지 않는 standalone 방식입니다. Python이 `/sys/class/net`, `/proc/diskstats`, `/sys/class/thermal`, 팬 hwmon 파일을 직접 읽습니다. 상태패널과 팬패널은 50ms 간격으로 커널 카운터를 따라가고, 그래프는 500ms 간격으로 샘플링합니다. 왼쪽 `DISK I/O`, `WAN`, `LAN`, `2.4G`, `5G` 표시등은 장식용 heartbeat가 아니라 직전 샘플 대비 현재 카운터 증가량이 0 초과인지에 반응합니다. 각 타일에는 글자 아래에 세 개의 작은 표시등을 두며 읽기/업로드은 초록, 쓰기/다운로드는 주황, 오류/드롭 증가는 빨강으로 표시합니다. 활동 표시등은 이벤트 감지 후 120ms 동안 유지하고, 오류 표시등은 2초 동안 유지해 너무 짧은 이벤트도 눈에 보이게 합니다. `DISK I/O`의 값은 `/dev/sda` Samsung T7을 `smartctl -d sntasmedia`로 1초마다 읽은 디스크 자체 온도입니다. GUI 사용자 `seo`는 `/etc/sudoers.d/control-touch-panel-smartctl`에서 해당 smartctl 명령만 NOPASSWD로 허용합니다. 네트워크 상태값은 정상, 드롭 누적, 꺼짐으로 표시합니다. 상태패널 최하단의 `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, CPU 온도, PWM, WAN 실시간 속도와 온도/RPM, WAN 트래픽 그래프를 표시합니다. 위 그래프는 좌측 Y축에 온도, 우측 Y축에 RPM 단위를 표시하고, 아래 WAN 그래프는 기존 형태를 유지합니다. 팬 auto/manual/off 버튼은 없고 PWM 슬라이더는 현재값 위치만 보여 주며 터치 제어는 받지 않습니다. +오른쪽에는 팬 RPM, CPU 온도, PWM, WAN 실시간 속도와 온도/RPM, WAN 트래픽 그래프를 표시합니다. WAN 속도와 그래프 Y축은 byte/sec 기준으로 `B/s`, `KB/s`, `MB/s`, `GB/s` 중 자동 변환합니다. 위 그래프는 좌측 Y축에 온도, 우측 Y축에 RPM 단위를 표시하고, 아래 WAN 그래프는 기존 형태를 유지합니다. 팬 auto/manual/off 버튼은 없고 PWM 슬라이더는 현재값 위치만 보여 주며 터치 제어는 받지 않습니다. GUI 자동 실행은 데스크톱 autostart가 1차 기준입니다. diff --git a/touch_panel/panel.py b/touch_panel/panel.py index 43c0bb5..427456e 100755 --- a/touch_panel/panel.py +++ b/touch_panel/panel.py @@ -31,6 +31,32 @@ PC_IP = "192.168.50.73" PC_MAC = "74:56:3C:CD:CD:79" +def byte_rate_unit(values): + max_value = max([abs(float(value)) for value in values] + [0.0]) + units = [ + (1024 ** 3, "GB/s"), + (1024 ** 2, "MB/s"), + (1024, "KB/s"), + ] + for factor, label in units: + if max_value >= factor: + return factor, label + return 1, "B/s" + + +def format_byte_rate(value, factor=None, unit=None): + if factor is None or unit is None: + factor, unit = byte_rate_unit([value]) + scaled = float(value) / factor + if abs(scaled) >= 100: + text = f"{scaled:.0f}" + elif abs(scaled) >= 10: + text = f"{scaled:.1f}" + else: + text = f"{scaled:.2f}" + return f"{text} {unit}" + + def read_text(path, default=""): try: return Path(path).read_text(encoding="utf-8").strip() @@ -322,13 +348,14 @@ class MetricCard(QFrame): class Sparkline(QFrame): - def __init__(self, title, color="#38bdf8", second_color=None, y_unit="", right_unit=""): + def __init__(self, title, color="#38bdf8", second_color=None, y_unit="", right_unit="", auto_byte_unit=False): super().__init__() self.setObjectName("chartCard") self.color = QColor(color) self.second_color = QColor(second_color) if second_color else None self.y_unit = y_unit self.right_unit = right_unit + self.auto_byte_unit = auto_byte_unit self.values = [] self.second_values = [] self.title = QLabel(title) @@ -366,17 +393,25 @@ class Sparkline(QFrame): painter.drawLine(rect.left(), rect.bottom(), rect.right(), rect.bottom()) if len(self.values) < 2: return - low = min(self.values) - high = max(self.values) + values = list(self.values) + second_values = list(self.second_values) + unit_factor = 1 + unit_label = self.y_unit + if self.auto_byte_unit: + unit_factor, unit_label = byte_rate_unit(values + second_values) + values = [value / unit_factor for value in values] + second_values = [value / unit_factor for value in second_values] + low = min(values) + high = max(values) if high <= low: high = low + 1 painter.setPen(QColor("#93a4bd")) - painter.drawText(1, rect.top() + 10, self.axis_text(high)) - painter.drawText(1, rect.bottom(), self.axis_text(low)) - self.draw_series(painter, rect, self.values, low, high, self.color) - if self.second_color and self.second_values: - second_low = min(self.second_values) - second_high = max(self.second_values) + painter.drawText(1, rect.top() + 10, self.axis_text(high, unit_label)) + painter.drawText(1, rect.bottom(), self.axis_text(low, unit_label)) + self.draw_series(painter, rect, values, low, high, self.color) + if self.second_color and second_values: + second_low = min(second_values) + second_high = max(second_values) if second_high <= second_low: second_high = second_low + 1 if self.right_unit: @@ -384,7 +419,9 @@ class Sparkline(QFrame): right_x = self.canvas.rect().right() - 31 painter.drawText(right_x, rect.top() + 10, self.axis_text(second_high, self.right_unit)) painter.drawText(right_x, rect.bottom(), self.axis_text(second_low, self.right_unit)) - self.draw_series(painter, rect, self.second_values, second_low, second_high, self.second_color) + self.draw_series(painter, rect, second_values, second_low, second_high, self.second_color) + else: + self.draw_series(painter, rect, second_values, low, high, self.second_color) def axis_text(self, value, unit=None): if abs(value) >= 100: @@ -524,7 +561,7 @@ class TouchPanel(QMainWindow): chart_grid.setVerticalSpacing(8) for index, (key, chart) in enumerate([ ("temp", Sparkline("온도 / RPM", "#f97316", "#38bdf8", "C", "rpm")), - ("wan", Sparkline("WAN 트래픽", "#38bdf8", "#22c55e", "M")), + ("wan", Sparkline("WAN 트래픽", "#38bdf8", "#22c55e", auto_byte_unit=True)), ]): self.charts[key] = chart chart_grid.addWidget(chart, index, 0) @@ -572,8 +609,8 @@ class TouchPanel(QMainWindow): } if previous: rates[key] = { - "rx": max(0.0, (current[0] - previous[0]) * 8 / dt / 1_000_000), - "tx": max(0.0, (current[1] - previous[1]) * 8 / dt / 1_000_000), + "rx": max(0.0, (current[0] - previous[0]) / dt), + "tx": max(0.0, (current[1] - previous[1]) / dt), } else: rates[key] = {"rx": 0.0, "tx": 0.0} @@ -593,7 +630,10 @@ class TouchPanel(QMainWindow): self.metrics["rpm"].set_value(f"{rpm:,}") self.metrics["temp"].set_value(f"{temp:.1f} C") self.metrics["pwm"].set_value(f"{pwm} / {percent:.1f}%") - self.metrics["wan"].set_value(f"{rates['wan']['rx']:.2f}/{rates['wan']['tx']:.2f} Mbps") + wan_factor, wan_unit = byte_rate_unit([rates["wan"]["rx"], rates["wan"]["tx"]]) + rx_text = format_byte_rate(rates["wan"]["rx"], wan_factor, wan_unit) + tx_text = format_byte_rate(rates["wan"]["tx"], wan_factor, wan_unit) + self.metrics["wan"].set_value(f"{rx_text}/{tx_text}") self.slider.blockSignals(True) self.slider.setValue(pwm) @@ -641,7 +681,7 @@ class TouchPanel(QMainWindow): self.net_history["wan"]["tx"].append(wan["tx"]) self.charts["wan"].set_values( self.net_history["wan"]["rx"], - f"{wan['rx']:.2f}/{wan['tx']:.2f} Mbps", + f"{format_byte_rate(wan['rx'])}/{format_byte_rate(wan['tx'])}", self.net_history["wan"]["tx"], )